From 589dcac5c48b65e87bf61bf8340910b1ed46f8b9 Mon Sep 17 00:00:00 2001
From: agentwall-agent
Date: Wed, 5 Aug 2026 00:47:38 -0500
Subject: [PATCH 1/2] release: v0.2.0 mechanics, packaging metadata, and a
checkable SBOM
Cutting the release becomes a tag push. release.yml builds the npm tarball,
five static agentwall-verify binaries, a CycloneDX SBOM, and checksums from
the tagged tree, attests them with SLSA provenance, and publishes to npm over
Trusted Publishing so no long-lived token exists to leak. workflow_dispatch
runs the same build and provenance and stops before publishing, so the
pipeline is proven while no tag exists.
Three failures the workflow now catches instead of shipping:
- The tag and package.json can disagree. They are compared before anything is
built, because every downstream artifact would otherwise carry a different
number than the release page.
- verifierVersion was a const, and the linker silently ignores -X on anything
that is not a string var. It is now a var, a test holds it in step with
package.json, and the workflow asks the built binary what it reports.
- cyclonedx-npm omits a package that is not hoisted to the top level while
keeping that package's own dependencies, which produced an SBOM with pino
missing and pino's dependency tail present. The SBOM is now checked against
npm's view of the production tree and a missing name fails the release.
Packaging: name becomes @reesebuilt/agentwall (the unscoped name belongs to an
unrelated project), version 0.2.0, engines floor 22.12.0, and the repository,
homepage and bugs fields several tools read. The files list stops shipping
2.4 MB of PNGs that nothing in the repository references.
Removed the direct pino dependency. Nothing imports it; the only occurrence in
src/ was a word in a comment. Fastify owns the logger and depends on pino
itself, and our declaration only pinned a second unused copy of pino 8 beside
it. Runtime dependencies are three, and a clean install drops 11 transitive
packages. @types/node tracks the supported floor at ^22 rather than the newest
release, since types describing APIs the runtime lacks compile and then crash.
Test Suites: 34 passed, 34 total
Tests: 300 passed, 300 total
---
.github/workflows/release.yml | 465 ++++++++++++++++++++++++++++++++++
CHANGELOG.md | 62 +++++
CONTRIBUTING.md | 5 +-
README.md | 27 +-
package-lock.json | 440 ++++++++------------------------
package.json | 24 +-
verifier/main_test.go | 29 +++
verifier/report.go | 10 +-
8 files changed, 712 insertions(+), 350 deletions(-)
create mode 100644 .github/workflows/release.yml
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..1ef0d2b
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,465 @@
+name: Release
+
+# Cutting a release is a tag push and nothing else. Every artifact below is produced from the
+# tagged tree by this workflow, so nothing a maintainer built on a laptop can reach the release.
+#
+# workflow_dispatch runs the same build and the same provenance over the same subjects, and stops
+# before anything is published. That makes the pipeline provable while no tag exists: a tag that
+# fails halfway has to be deleted and re-pushed, and a deleted tag is a bad look on a project
+# whose pitch is durable evidence.
+on:
+ push:
+ tags:
+ - "v*"
+ workflow_dispatch:
+
+# Workflow-wide floor. A job added later that forgets its own block inherits read-only instead of
+# the repository default, which on many repositories is read-write: a compromised step would
+# otherwise hold a token that can push commits, move tags, or open releases.
+permissions:
+ contents: read
+
+env:
+ # Exact, not a range. A stranger who rebuilds the verifier and compares checksums is comparing
+ # against these bytes, and Go's output changes between patch releases. `go-version-file` is
+ # right for ci.yml, where the question is "does this still build under the Go we claim", and
+ # wrong here, where the question is "does this produce the same bytes as the release".
+ GO_VERSION: "1.22.12"
+ # The engines floor, not the latest Node. Building and testing the release on the oldest
+ # runtime we advertise is the only thing that keeps that claim from being decorative.
+ NODE_VERSION: "22.12.0"
+ # Release tooling, pinned and invoked through npx. It is not a devDependency because it is not
+ # a build input: nothing in src/ or tests/ needs it, and adding it would put a large tree into
+ # every contributor's install to serve one step that runs a few times a year.
+ CYCLONEDX_VERSION: "6.0.0"
+ # npm Trusted Publishing over OIDC needs npm 11.5.1 or newer. Node 22.12 ships npm 10, which
+ # fails with an authentication error that reads like a misconfigured token rather than an old
+ # client, so the version is pinned here instead of being inherited and hoped about.
+ #
+ # Held on the 11.x line on purpose. npm 12 declares engines ^22.22.2 || ^24.15.0 || >=26.0.0,
+ # which the 22.12.0 floor above does not satisfy, so installing it here would fail on a real
+ # tag and only on a real tag. 11.19.0 accepts ^20.17.0 || >=22.9.0 and clears the OIDC floor.
+ NPM_VERSION: "11.19.0"
+ IMAGE_NAME: ghcr.io/reesebuilt/agentwall
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ outputs:
+ version: ${{ steps.meta.outputs.version }}
+ tag: ${{ steps.meta.outputs.tag }}
+ tarball: ${{ steps.meta.outputs.tarball }}
+ is-release: ${{ steps.meta.outputs.is-release }}
+ hashes: ${{ steps.checksums.outputs.hashes }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+
+ - name: Setup Node
+ uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: npm
+
+ - name: Setup Go
+ uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
+ with:
+ go-version: ${{ env.GO_VERSION }}
+ # Zero requirements means no dependency graph to cache and no go.sum for the cache key
+ # to hash. setup-go's cache path wants that file and warns when it is absent.
+ cache: false
+
+ # A tag names a version and package.json names a version. When they disagree, every
+ # downstream artifact disagrees with something: the tarball says one number, the release
+ # page says another, and the verifier binary says a third. Fail here, where the only cost
+ # is deleting an unpushed tag.
+ - name: Resolve version
+ id: meta
+ run: |
+ set -euo pipefail
+ pkg_version="$(node -p 'require("./package.json").version')"
+ if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
+ tag="${GITHUB_REF_NAME}"
+ version="${tag#v}"
+ if [ "$version" != "$pkg_version" ]; then
+ echo "tag ${tag} implies version ${version}, but package.json says ${pkg_version}." >&2
+ echo "bump package.json (and verifier/report.go) or move the tag; do not ship both numbers." >&2
+ exit 1
+ fi
+ is_release=true
+ else
+ version="$pkg_version"
+ tag="v${version}"
+ is_release=false
+ fi
+ {
+ echo "version=${version}"
+ echo "tag=${tag}"
+ echo "is-release=${is_release}"
+ echo "tarball=reesebuilt-agentwall-${version}.tgz"
+ } >> "$GITHUB_OUTPUT"
+ echo "building ${tag} (release=${is_release})"
+
+ - name: Install
+ run: npm ci
+
+ - name: Lint (type-check)
+ run: npm run lint
+
+ - name: Build
+ run: npm run build
+
+ - name: Test
+ run: npm test
+
+ - name: Build verifier for conformance
+ run: go build -o agentwall-verify .
+ working-directory: verifier
+
+ # The release's headline claim is that a second implementation agrees about the evidence
+ # format. Re-running the corpus here means the binaries attached to the release are the
+ # same ones that agreed, rather than binaries built from a tree that merely passed CI once.
+ - name: Conformance (typescript and go)
+ run: node scripts/conformance.js
+
+ - name: Stage release directory
+ run: mkdir -p dist-release
+
+ # Publishing the exact bytes that were checksummed and attested, rather than re-packing at
+ # publish time, is the whole point: otherwise the provenance describes an artifact nobody
+ # can prove is the one on the registry.
+ - name: Pack npm tarball
+ run: |
+ set -euo pipefail
+ npm pack --pack-destination dist-release
+ test -f "dist-release/${TARBALL}"
+ env:
+ TARBALL: ${{ steps.meta.outputs.tarball }}
+
+ - name: Generate CycloneDX SBOM
+ run: |
+ set -euo pipefail
+ npx --yes "@cyclonedx/cyclonedx-npm@${CYCLONEDX_VERSION}" \
+ --omit dev \
+ --output-file dist-release/sbom.cdx.json
+
+ # An SBOM nobody checks is decoration. cyclonedx-npm builds its component list from the
+ # installed tree, and a package that is not hoisted to the top level can be dropped from
+ # that list while its own dependencies are kept, which produces a plausible-looking SBOM
+ # with a hole in it. This compares the SBOM against npm's own view of the production tree
+ # and fails when a package name is absent entirely.
+ #
+ # Known limit, deliberately not fatal: when one package is installed at two versions, the
+ # generator lists one of them. The name is still present, so a reader knows the dependency
+ # exists; a missing name is the failure that hides a whole component, and that is what this
+ # gate catches.
+ - name: SBOM covers the production tree
+ run: |
+ set -euo pipefail
+ npm ls --omit=dev --all --json > npm-tree.json
+ node - <<'NODE'
+ const fs = require("node:fs");
+ const tree = JSON.parse(fs.readFileSync("npm-tree.json", "utf8"));
+ const sbom = JSON.parse(fs.readFileSync("dist-release/sbom.cdx.json", "utf8"));
+ const installed = new Map();
+ (function walk(node) {
+ for (const [name, child] of Object.entries(node.dependencies ?? {})) {
+ if (!installed.has(name)) installed.set(name, new Set());
+ installed.get(name).add(child.version);
+ walk(child);
+ }
+ })(tree);
+ const listed = new Set(
+ (sbom.components ?? []).map((c) => (c.group ? `${c.group}/${c.name}` : c.name))
+ );
+ const missing = [...installed.keys()].filter((name) => !listed.has(name));
+ const partial = [...installed.entries()]
+ .filter(([name, versions]) => listed.has(name) && versions.size > 1)
+ .map(([name, versions]) => `${name} (${[...versions].join(", ")})`);
+ console.log(`npm reports ${installed.size} production packages; SBOM lists ${listed.size} components.`);
+ if (partial.length > 0) {
+ console.log(`installed at multiple versions, one recorded: ${partial.join("; ")}`);
+ }
+ if (missing.length > 0) {
+ console.error(`SBOM omits these production packages entirely: ${missing.join(", ")}`);
+ process.exit(1);
+ }
+ NODE
+
+ # CGO_ENABLED=0 makes the binaries static, so they run on a machine with no toolchain and
+ # no libc of a particular vintage. -trimpath removes the builder's absolute paths, which is
+ # what makes two people's builds of the same source produce the same bytes.
+ - name: Cross-compile verifier binaries
+ run: |
+ set -euo pipefail
+ for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64; do
+ goos="${target%/*}"
+ goarch="${target#*/}"
+ ext=""
+ [ "$goos" = "windows" ] && ext=".exe"
+ CGO_ENABLED=0 GOOS="$goos" GOARCH="$goarch" GOTOOLCHAIN=local \
+ go build -trimpath \
+ -ldflags "-s -w -X main.verifierVersion=${VERSION}" \
+ -o "../dist-release/agentwall-verify-${goos}-${goarch}${ext}" .
+ done
+ working-directory: verifier
+ env:
+ VERSION: ${{ steps.meta.outputs.version }}
+
+ # The -X flag is silently ignored when it names a symbol that is not a string var, so a
+ # refactor that turns verifierVersion back into a const would produce binaries reporting a
+ # stale version with a green build. Asking the binary is the only way to know it took.
+ - name: Verifier binary reports the release version
+ run: |
+ set -euo pipefail
+ chmod +x dist-release/agentwall-verify-linux-amd64
+ reported="$(dist-release/agentwall-verify-linux-amd64 --version)"
+ echo "$reported"
+ if [ "$reported" != "agentwall-verify ${VERSION}" ]; then
+ echo "binary reports '${reported}', expected 'agentwall-verify ${VERSION}'." >&2
+ echo "the -ldflags stamp did not take; check that verifierVersion is a var." >&2
+ exit 1
+ fi
+ env:
+ VERSION: ${{ steps.meta.outputs.version }}
+
+ # Sorted with a fixed collation so the file is byte-identical across runs of the same
+ # inputs, and so the SLSA subject list below is stable.
+ #
+ # The file list is captured into an array before checksums.txt is opened, because a shell
+ # redirection creates its target before the command on the left runs: staging the list in
+ # a file inside this directory put that staging file into its own checksum list, and
+ # `sha256sum -c` then failed on a correct download by looking for a file the release does
+ # not contain.
+ - name: Checksums
+ id: checksums
+ run: |
+ set -euo pipefail
+ cd dist-release
+ mapfile -t files < <(
+ find . -maxdepth 1 -type f ! -name checksums.txt -printf '%P\n' | LC_ALL=C sort
+ )
+ sha256sum "${files[@]}" > checksums.txt
+ cat checksums.txt
+ echo "hashes=$(base64 -w0 < checksums.txt)" >> "$GITHUB_OUTPUT"
+
+ - name: Upload release artifacts
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: release-artifacts
+ path: dist-release/
+ # An upload that silently matches nothing leaves a green job and no artifact, which is
+ # the failure mode this step exists to make visible.
+ if-no-files-found: error
+
+ # Exception to the SHA-pinning rule, and the only one in this repository: the SLSA generator
+ # resolves its own ref against the release tag it was published under and refuses to run when
+ # it is called by digest, so a SHA pin here fails closed rather than merely going unverified.
+ # The tag is what its own verification expects, and slsa-verifier checks the builder identity
+ # afterwards, which is the property the pin would have been protecting.
+ provenance:
+ needs: [build]
+ permissions:
+ # Read the workflow path that goes into the attestation.
+ actions: read
+ # Sign the provenance against the workflow's own OIDC identity.
+ id-token: write
+ # The reusable workflow declares this for its own upload path; assets are attached by the
+ # release job below, which keeps one job in charge of what the release contains.
+ contents: write
+ uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
+ with:
+ base64-subjects: ${{ needs.build.outputs.hashes }}
+ upload-assets: false
+
+ release:
+ needs: [build, provenance]
+ if: needs.build.outputs.is-release == 'true'
+ runs-on: ubuntu-latest
+ permissions:
+ # Create the release and attach assets to it.
+ contents: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+
+ - name: Download build artifacts
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: release-artifacts
+ path: dist-release
+
+ - name: Download provenance
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.provenance.outputs.provenance-name }}
+ path: dist-release
+
+ # The changelog section is the release notes. Keeping one source means the release page
+ # cannot drift into marketing that the repository does not say.
+ - name: Compose release notes
+ run: |
+ set -euo pipefail
+ awk -v ver="$VERSION" '
+ $0 ~ "^## \\[" ver "\\]" { inside = 1; next }
+ inside && /^## \[/ { exit }
+ inside { print }
+ ' CHANGELOG.md > notes.md
+ if [ ! -s notes.md ]; then
+ echo "CHANGELOG.md has no [${VERSION}] section; the release would have empty notes." >&2
+ exit 1
+ fi
+ cat >> notes.md < v.split(".").map(Number);
+ const [a, b] = [parse(have), parse(floor)];
+ const older = a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
+ if (older < 0) {
+ console.error(`npm ${have} cannot use Trusted Publishing; ${floor} is the floor.`);
+ console.error("NPM_VERSION did not install, most likely because this node fails its engines range.");
+ process.exit(1);
+ }
+ ' "$actual"
+
+ - name: Download build artifacts
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: release-artifacts
+ path: dist-release
+
+ # Publishing the packed tarball, not the working tree, is what makes the checksum and the
+ # SLSA attestation above describe the artifact that actually lands on the registry.
+ - name: Publish to npm
+ run: npm publish "dist-release/${TARBALL}" --provenance --access public
+ env:
+ TARBALL: ${{ needs.build.outputs.tarball }}
+
+ docker:
+ needs: [build, release]
+ if: needs.build.outputs.is-release == 'true'
+ runs-on: ubuntu-latest
+ permissions:
+ # Push the image to GitHub Container Registry.
+ packages: write
+ # Keyless cosign signing against the workflow's OIDC identity, same reasoning as npm above.
+ id-token: write
+ contents: read
+ steps:
+ - name: Checkout
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+
+ - name: Log in to GitHub Container Registry
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Image metadata
+ id: metadata
+ uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
+ with:
+ images: ${{ env.IMAGE_NAME }}
+ tags: |
+ type=raw,value=${{ needs.build.outputs.tag }}
+ type=raw,value=latest
+
+ - name: Build and push
+ id: push
+ uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
+ with:
+ context: .
+ file: ./Dockerfile
+ # One architecture, because one architecture is what gets built and exercised. An
+ # arm64 manifest nobody has run is a support claim, not a build artifact.
+ platforms: linux/amd64
+ push: true
+ tags: ${{ steps.metadata.outputs.tags }}
+ labels: ${{ steps.metadata.outputs.labels }}
+ provenance: true
+
+ - name: Install cosign
+ uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
+
+ # Sign the digest, not the tag. A tag is a mutable pointer, so a signature over a tag says
+ # nothing about which bytes were signed once the tag moves.
+ - name: Sign the image
+ run: cosign sign --yes "${IMAGE_NAME}@${DIGEST}"
+ env:
+ DIGEST: ${{ steps.push.outputs.digest }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ead4db7..1a98032 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,50 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
## [Unreleased]
+## [0.2.0] - 2026-08-05
+
+The first tagged release. It freezes the on-disk evidence format and makes that format
+checkable by a program that shares no code with Agentwall.
+
+Upgrade if you care whether the audit log can be quietly rewritten. Before this release the
+hash chain was verifiable only by the same TypeScript that wrote it, which means a bug in the
+writer was invisible to the reader. Now the format is specified in `docs/audit-format.md`, a
+second implementation in Go verifies it, and the two are held against a shared corpus on every
+commit. Existing audit files stay verifiable: records written before this release carry no
+canonical-form marker and are still accepted through the legacy path.
+
+Nothing about the shipped posture changed. Monitor-first is still the default, and no default
+decision, policy file, or enforcement behavior moved in this release.
+
### Added
+- Canonical form `cu1`, recorded in each record's `integrity.canon`. Object keys are ordered by
+ UTF-16 code unit rather than locale collation, so a verifier in another language reproduces
+ the hash without shipping ICU tables. Records without the marker are hashed under the old
+ locale-dependent order and remain verifiable through a fallback path.
+- A rotation manifest, `segments.jsonl`, that binds every sealed segment to its own bytes. Each
+ entry carries a hash of itself and the final hash of the segment before it, so deleting a
+ rotated segment, reordering two of them, or rewriting one after it was sealed is now
+ detectable rather than silently invisible.
+- Live-tail re-derivation. A checkpoint commits to a prefix of the live file, so the log growing
+ after a checkpoint is normal and does not invalidate it, while rewriting any record inside the
+ committed prefix fails verification.
+- Rejection of records containing duplicate JSON keys. Implementations disagree about which
+ value of a repeated member wins, so a duplicate key is a way to hand two readers the same
+ bytes and have them reach different conclusions about what was recorded.
+- Off-box anchoring wired to the CLI: `agentwall anchor` seals the current segment, signs an
+ Ed25519 checkpoint over the composite state, and submits it to OpenTimestamps calendars.
+ `agentwall verify` reports the chained, linked, and anchored layers separately and exits
+ non-zero unless all three hold.
+- An independent verifier, `agentwall-verify`, written in Go against `docs/audit-format.md`
+ rather than against our source. It uses the Go standard library and nothing else, so
+ `go list -m all` prints one line, and it performs no network access and writes no files.
+ Released as static binaries for linux, macOS, and Windows.
+- A 26 case conformance corpus covering valid evidence, forgeries, and boundary conditions, run
+ through both verifiers on every commit. The two agree on 22 cases; the 4 remaining are places
+ the bundled TypeScript verifier accepts evidence the format rejects, declared explicitly in
+ the harness rather than papered over.
+- `docs/audit-format.md`, the normative specification. The implementations conform to it, not
+ the other way around.
- FloodGuard shield mode control surface in the dashboard.
- FloodGuard per-session temporary override API and operator controls.
- Forward-facing Agentwall logo assets wired into README and public HTML surfaces.
@@ -15,6 +58,10 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
- Approval webhook notifications for queued and resolved manual reviews via `approval.webhookUrl`.
### Changed
+- The npm package is published as `@reesebuilt/agentwall`. The unscoped name `agentwall` on npm
+ belongs to an unrelated project and always has. The installed command is still `agentwall`.
+- The supported Node floor is 22.12.0, declared in `engines`. Node 20 reached end of life in
+ April 2026 and a security tool should not advertise a runtime that stops receiving fixes.
- CLI terminate now requires `--confirm` so hard containment is deliberate instead of one typo away.
- Live-control docs now point to the monitor-first example on port `3015` to avoid false 401/404 debugging on the wrong local service.
- Session control CLI errors now explain how to recover from `Session not found` by seeding a live runtime session first.
@@ -44,6 +91,21 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
- Policy and config YAML now parses under the YAML 1.2 core schema on js-yaml 5. Merge keys (`<<`) are no longer expanded, so a policy file that relies on one is rejected whole and the last good ruleset stays in force instead of a partially assembled rule taking effect. Unquoted dates load as strings rather than `Date` objects, and a mapping with a complex key is rejected instead of having that key flattened into a lossy string.
- The dashboard runtime-context panel now degrades to "none" when the agent harness config file cannot be parsed, rather than failing the whole dashboard state build on a file Agentwall does not own.
+### Fixed
+- OpenTimestamps proof files are named after the checkpoint digest instead of a counter that
+ only advances on rotation. Two anchor passes without a rotation between them previously wrote
+ the same filename, so each pass destroyed the proof the previous one had obtained. Anchoring
+ on a schedule would have erased evidence at every interval.
+- `agentwall verify` no longer reports "nothing anchored off-box yet" when the anchor log has
+ entries. It reached that message whenever the anchor log and the checkpoint key were not both
+ present, and said it in a state where it was false.
+
+### Removed
+- The direct `pino` dependency, which nothing imported. Fastify owns the logger instance the
+ server actually uses and depends on pino itself, so the declared dependency only pinned a
+ second, unused copy of pino 8 in the tree. Runtime dependencies are now three, `fastify`,
+ `js-yaml`, and `zod`, and a clean install carries 11 fewer transitive packages.
+
## [0.1.0] - 2026-03-23
### Added
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b43b39f..4dc1414 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -44,8 +44,9 @@ worse than no tool.
## Dependencies
-Runtime dependencies are deliberately four: `fastify`, `js-yaml`, `pino`, `zod`. Adding a fifth
-needs a reason in the pull request.
+Runtime dependencies are deliberately three: `fastify`, `js-yaml`, `zod`. Adding a fourth needs
+a reason in the pull request. A dependency nothing imports is not a dependency; declare what the
+code actually uses.
The audit, signing, and anchoring paths use Node's own `crypto` and plain HTTP with no
third-party clients. Do not add a dependency there. A supply-chain compromise inside the
diff --git a/README.md b/README.md
index 7a9a5c6..f4645b0 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ and keeps a record that cannot be quietly rewritten.
-
+
@@ -53,7 +53,19 @@ The rest of the limits are in [Limits](#limits). They are not footnotes.
## Quick start
-Linux, Node.js 20 or newer. Verified on Node 24.14.1.
+Linux, Node.js 22.12 or newer. Verified on Node 24.14.1.
+
+```bash
+npm install -g @reesebuilt/agentwall
+
+agentwall init --mode monitor
+agentwall doctor
+```
+
+The npm package named `agentwall`, without a scope, is a different and unrelated project. This
+one is `@reesebuilt/agentwall`; the command it installs is `agentwall`.
+
+From a checkout instead:
```bash
git clone https://github.com/reesebuilt/agentwall.git
@@ -634,11 +646,12 @@ Egress observed by the proxy enters the same hash chain, attributed to the origi
## Built with
-TypeScript 5 (strict) on Node.js 20+, Fastify 5, Zod, pino, YAML policy via `js-yaml`, Jest.
-Runtime dependencies are deliberately four: `fastify`, `js-yaml`, `pino`, `zod`. The audit and
-anchoring paths use Node's own `crypto` and plain HTTP with no third-party clients, because a
-dependency inside the component whose entire job is being trustworthy is a supply-chain risk
-this project declines.
+TypeScript 5 (strict) on Node.js 22.12+, Fastify 5, Zod, YAML policy via `js-yaml`, Jest.
+Runtime dependencies are deliberately three: `fastify`, `js-yaml`, `zod`. Logging is Fastify's
+own pino instance, which arrives as its dependency rather than ours. The audit and anchoring
+paths use Node's own `crypto` and plain HTTP with no third-party clients, because a dependency
+inside the component whose entire job is being trustworthy is a supply-chain risk this project
+declines.
## Docs
diff --git a/package-lock.json b/package-lock.json
index ea1850c..3b2231e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,17 +1,16 @@
{
- "name": "agentwall",
- "version": "0.1.0",
+ "name": "@reesebuilt/agentwall",
+ "version": "0.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "agentwall",
- "version": "0.1.0",
+ "name": "@reesebuilt/agentwall",
+ "version": "0.2.0",
"license": "Apache-2.0",
"dependencies": {
"fastify": "5.11.0",
"js-yaml": "5.2.3",
- "pino": "^8.19.0",
"zod": "4.4.3"
},
"bin": {
@@ -19,11 +18,14 @@
},
"devDependencies": {
"@jest/globals": "^30.4.1",
- "@types/node": "^20.11.5",
+ "@types/node": "^22.20.1",
"jest": "^30.4.2",
"ts-jest": "^29.4.12",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
+ },
+ "engines": {
+ "node": ">=22.12.0"
}
},
"node_modules/@babel/code-frame": {
@@ -638,9 +640,9 @@
"license": "MIT"
},
"node_modules/@fastify/fast-json-stringify-compiler": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz",
- "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.1.0.tgz",
+ "integrity": "sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw==",
"funding": [
{
"type": "github",
@@ -653,13 +655,13 @@
],
"license": "MIT",
"dependencies": {
- "fast-json-stringify": "^6.0.0"
+ "fast-json-stringify": "^7.0.0"
}
},
"node_modules/@fastify/forwarded": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz",
- "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.2.tgz",
+ "integrity": "sha512-NE8HgKLgYejV9lDpqkEFaDKMLYelJBVfHekhB0UKvX0ghagXRJqg68feg8er1NPXxG4N9i6vPxzt8E+3wHfcmA==",
"funding": [
{
"type": "github",
@@ -1362,9 +1364,9 @@
}
},
"node_modules/@types/node": {
- "version": "20.19.37",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
- "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
+ "version": "22.20.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz",
+ "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1745,18 +1747,6 @@
"win32"
]
},
- "node_modules/abort-controller": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
- "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
- "license": "MIT",
- "dependencies": {
- "event-target-shim": "^5.0.0"
- },
- "engines": {
- "node": ">=6.5"
- }
- },
"node_modules/abstract-logging": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
@@ -1764,9 +1754,9 @@
"license": "MIT"
},
"node_modules/acorn": {
- "version": "8.16.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
- "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
+ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -1790,9 +1780,9 @@
}
},
"node_modules/ajv": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
- "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@@ -1917,9 +1907,9 @@
}
},
"node_modules/avvio": {
- "version": "9.2.0",
- "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.2.0.tgz",
- "integrity": "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==",
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.3.0.tgz",
+ "integrity": "sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A==",
"funding": [
{
"type": "github",
@@ -2042,26 +2032,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/baseline-browser-mapping": {
"version": "2.11.12",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz",
@@ -2142,30 +2112,6 @@
"node-int64": "^0.4.0"
}
},
- "node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
- }
- },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -2509,9 +2455,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.400",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.400.tgz",
- "integrity": "sha512-96EWDNjM59SYflgeV5Ylsf4EMiq1a25YjCnJH7cxn/AF2H3pILRweaUnoLax0yKHWdpOzY6JKEu45e8irqZIHA==",
+ "version": "1.5.401",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.401.tgz",
+ "integrity": "sha512-H6ViHN68nGYlChEvlIU67fn8O2/tpbWQPwck98yaJmh+08LSvHiydzDQ6oXNccLU3kNRVIRS9A4mA7CG+i6fLQ==",
"dev": true,
"license": "ISC"
},
@@ -2579,24 +2525,6 @@
"node": ">=4"
}
},
- "node_modules/event-target-shim": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
- "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/events": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
- "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
- "license": "MIT",
- "engines": {
- "node": ">=0.8.x"
- }
- },
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@@ -2676,9 +2604,9 @@
"license": "MIT"
},
"node_modules/fast-json-stringify": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.3.0.tgz",
- "integrity": "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-7.0.1.tgz",
+ "integrity": "sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==",
"funding": [
{
"type": "github",
@@ -2694,11 +2622,27 @@
"@fastify/merge-json-schemas": "^0.2.0",
"ajv": "^8.12.0",
"ajv-formats": "^3.0.1",
- "fast-uri": "^3.0.0",
+ "fast-uri": "^4.0.0",
"json-schema-ref-resolver": "^3.0.0",
"rfdc": "^1.2.0"
}
},
+ "node_modules/fast-json-stringify/node_modules/fast-uri": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.2.tgz",
+ "integrity": "sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/fast-querystring": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz",
@@ -2708,15 +2652,6 @@
"fast-decode-uri-component": "^1.0.1"
}
},
- "node_modules/fast-redact": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
- "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/fast-uri": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
@@ -2766,117 +2701,6 @@
"toad-cache": "^3.7.0"
}
},
- "node_modules/fastify/node_modules/fast-json-stringify": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-7.0.1.tgz",
- "integrity": "sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@fastify/merge-json-schemas": "^0.2.0",
- "ajv": "^8.12.0",
- "ajv-formats": "^3.0.1",
- "fast-uri": "^4.0.0",
- "json-schema-ref-resolver": "^3.0.0",
- "rfdc": "^1.2.0"
- }
- },
- "node_modules/fastify/node_modules/fast-uri": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.2.tgz",
- "integrity": "sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "BSD-3-Clause"
- },
- "node_modules/fastify/node_modules/pino": {
- "version": "9.14.0",
- "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz",
- "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==",
- "license": "MIT",
- "dependencies": {
- "@pinojs/redact": "^0.4.0",
- "atomic-sleep": "^1.0.0",
- "on-exit-leak-free": "^2.1.0",
- "pino-abstract-transport": "^2.0.0",
- "pino-std-serializers": "^7.0.0",
- "process-warning": "^5.0.0",
- "quick-format-unescaped": "^4.0.3",
- "real-require": "^0.2.0",
- "safe-stable-stringify": "^2.3.1",
- "sonic-boom": "^4.0.1",
- "thread-stream": "^3.0.0"
- },
- "bin": {
- "pino": "bin.js"
- }
- },
- "node_modules/fastify/node_modules/pino-abstract-transport": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
- "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
- "license": "MIT",
- "dependencies": {
- "split2": "^4.0.0"
- }
- },
- "node_modules/fastify/node_modules/pino-std-serializers": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
- "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
- "license": "MIT"
- },
- "node_modules/fastify/node_modules/process-warning": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
- "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "MIT"
- },
- "node_modules/fastify/node_modules/sonic-boom": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
- "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
- "license": "MIT",
- "dependencies": {
- "atomic-sleep": "^1.0.0"
- }
- },
- "node_modules/fastify/node_modules/thread-stream": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
- "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
- "license": "MIT",
- "dependencies": {
- "real-require": "^0.2.0"
- }
- },
"node_modules/fastq": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
@@ -3084,26 +2908,6 @@
"node": ">=10.17.0"
}
},
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "BSD-3-Clause"
- },
"node_modules/import-local": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
@@ -3154,9 +2958,9 @@
"license": "ISC"
},
"node_modules/ipaddr.js": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz",
- "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz",
+ "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==",
"license": "MIT",
"engines": {
"node": ">= 10"
@@ -4401,41 +4205,40 @@
}
},
"node_modules/pino": {
- "version": "8.21.0",
- "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz",
- "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==",
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
+ "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
"license": "MIT",
"dependencies": {
+ "@pinojs/redact": "^0.4.0",
"atomic-sleep": "^1.0.0",
- "fast-redact": "^3.1.1",
"on-exit-leak-free": "^2.1.0",
- "pino-abstract-transport": "^1.2.0",
- "pino-std-serializers": "^6.0.0",
- "process-warning": "^3.0.0",
+ "pino-abstract-transport": "^3.0.0",
+ "pino-std-serializers": "^7.0.0",
+ "process-warning": "^5.0.0",
"quick-format-unescaped": "^4.0.3",
"real-require": "^0.2.0",
"safe-stable-stringify": "^2.3.1",
- "sonic-boom": "^3.7.0",
- "thread-stream": "^2.6.0"
+ "sonic-boom": "^4.0.1",
+ "thread-stream": "^4.0.0"
},
"bin": {
"pino": "bin.js"
}
},
"node_modules/pino-abstract-transport": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz",
- "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
+ "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
"license": "MIT",
"dependencies": {
- "readable-stream": "^4.0.0",
"split2": "^4.0.0"
}
},
"node_modules/pino-std-serializers": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz",
- "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
+ "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
"license": "MIT"
},
"node_modules/pirates": {
@@ -4490,19 +4293,20 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/process": {
- "version": "0.11.10",
- "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
- "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6.0"
- }
- },
"node_modules/process-warning": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz",
- "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz",
+ "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
"license": "MIT"
},
"node_modules/pure-rand": {
@@ -4544,22 +4348,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/readable-stream": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
- "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
- "license": "MIT",
- "dependencies": {
- "abort-controller": "^3.0.0",
- "buffer": "^6.0.3",
- "events": "^3.3.0",
- "process": "^0.11.10",
- "string_decoder": "^1.3.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
- },
"node_modules/real-require": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
@@ -4636,30 +4424,10 @@
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"license": "MIT"
},
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/safe-regex2": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.0.tgz",
- "integrity": "sha512-pNHAuBW7TrcleFHsxBr5QMi/Iyp0ENjUKz7GCcX1UO7cMh+NmVK6HxQckNL1tJp1XAJVjG6B8OKIPqodqj9rtw==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz",
+ "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==",
"funding": [
{
"type": "github",
@@ -4768,9 +4536,9 @@
}
},
"node_modules/sonic-boom": {
- "version": "3.8.1",
- "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz",
- "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
+ "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
"license": "MIT",
"dependencies": {
"atomic-sleep": "^1.0.0"
@@ -4826,15 +4594,6 @@
"node": ">=10"
}
},
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
"node_modules/string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
@@ -5100,14 +4859,23 @@
}
},
"node_modules/thread-stream": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz",
- "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz",
+ "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==",
"license": "MIT",
"dependencies": {
- "real-require": "^0.2.0"
+ "real-require": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=20"
}
},
+ "node_modules/thread-stream/node_modules/real-require": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz",
+ "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==",
+ "license": "MIT"
+ },
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -5116,12 +4884,12 @@
"license": "BSD-3-Clause"
},
"node_modules/toad-cache": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz",
- "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==",
+ "version": "3.7.4",
+ "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz",
+ "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==",
"license": "MIT",
"engines": {
- "node": ">=12"
+ "node": ">=20"
}
},
"node_modules/ts-jest": {
diff --git a/package.json b/package.json
index f7c2f8c..fa75c6e 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,18 @@
{
- "name": "agentwall",
- "version": "0.1.0",
+ "name": "@reesebuilt/agentwall",
+ "version": "0.2.0",
"description": "Agentwall: provenance-aware policy enforcement for agent egress, tools, content, and approvals",
+ "homepage": "https://github.com/reesebuilt/agentwall#readme",
+ "bugs": {
+ "url": "https://github.com/reesebuilt/agentwall/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/reesebuilt/agentwall.git"
+ },
"main": "dist/index.js",
"scripts": {
+ "prepack": "npm run build",
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts",
@@ -27,15 +36,21 @@
"ai-safety"
],
"license": "Apache-2.0",
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "publishConfig": {
+ "access": "public",
+ "provenance": true
+ },
"dependencies": {
"fastify": "5.11.0",
"js-yaml": "5.2.3",
- "pino": "^8.19.0",
"zod": "4.4.3"
},
"devDependencies": {
"@jest/globals": "^30.4.1",
- "@types/node": "^20.11.5",
+ "@types/node": "^22.20.1",
"jest": "^30.4.2",
"ts-jest": "^29.4.12",
"ts-node": "^10.9.2",
@@ -57,6 +72,7 @@
"files": [
"dist/",
"public/",
+ "!public/assets/*.png",
"examples/",
"README.md",
"LICENSE",
diff --git a/verifier/main_test.go b/verifier/main_test.go
index e112fd6..8a9977b 100644
--- a/verifier/main_test.go
+++ b/verifier/main_test.go
@@ -3,6 +3,8 @@ package main
import (
"bytes"
"encoding/json"
+ "errors"
+ "os"
"path/filepath"
"strings"
"testing"
@@ -28,6 +30,33 @@ func TestRunVersion(t *testing.T) {
}
}
+// The release stamps the tag into the binary via -ldflags, but `go run ./verifier` from a plain
+// checkout reports the compiled-in default instead. That default is a second copy of the
+// project's version number, and a second copy drifts: bump package.json, forget report.go, and
+// the verifier tells a stranger it is a version that was never released. This test is the only
+// thing that notices.
+func TestVersionMatchesPackageJSON(t *testing.T) {
+ raw, err := os.ReadFile(filepath.Join("..", "package.json"))
+ if errors.Is(err, os.ErrNotExist) {
+ t.Skip("no package.json beside the verifier; running outside the repo")
+ }
+ if err != nil {
+ t.Fatalf("reading package.json: %v", err)
+ }
+ var pkg struct {
+ Version string `json:"version"`
+ }
+ if err := json.Unmarshal(raw, &pkg); err != nil {
+ t.Fatalf("parsing package.json: %v", err)
+ }
+ if pkg.Version == "" {
+ t.Fatal("package.json has no version field")
+ }
+ if pkg.Version != verifierVersion {
+ t.Fatalf("verifierVersion %q does not match package.json version %q; update verifier/report.go", verifierVersion, pkg.Version)
+ }
+}
+
func TestRunMissingAuditFileIsIOError(t *testing.T) {
var out, errb bytes.Buffer
if code := run([]string{"--audit", filepath.Join(t.TempDir(), "nope.jsonl")}, &out, &errb); code != 2 {
diff --git a/verifier/report.go b/verifier/report.go
index 5717cb0..17a7c71 100644
--- a/verifier/report.go
+++ b/verifier/report.go
@@ -17,11 +17,19 @@ import (
const (
verifierName = "agentwall-verify"
- verifierVersion = "0.2.0"
verifierLanguage = "go"
verifierCanon = "cu1"
)
+// verifierVersion is a var rather than a const so a release build can stamp the tag into the
+// binary with -ldflags "-X main.verifierVersion=". The linker refuses to write to a
+// const, and it silently ignores a -X naming a symbol that is not a string var, so declaring
+// this as a const produces a binary that reports a stale version with no build error to catch
+// it: the release would ship a v0.3.0 binary that answers "0.2.0". The default matches the
+// version field in package.json, which keeps `go run ./verifier` honest from a plain checkout
+// where no linker flags are set. TestVersionMatchesPackageJSON holds the two in step.
+var verifierVersion = "0.2.0"
+
// problem is one finding. code is stable and machine-readable and appears in JSON output; text
// is human guidance and is explicitly not part of any cross-implementation contract. fatal
// distinguishes a finding that fails its layer from one that is merely reported, such as a torn
From ddd7365208f37923d3accd7aeb1c57e51538e730 Mon Sep 17 00:00:00 2001
From: agentwall-agent
Date: Wed, 5 Aug 2026 00:49:33 -0500
Subject: [PATCH 2/2] release: drop contents:write from the provenance job
The generator's upload-assets job owns that permission and is guarded by
`if: inputs.upload-assets`, which is false here, so the caller granting it
was privilege the run never uses. The release job attaches the provenance.
---
.github/workflows/release.yml | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1ef0d2b..e4132ff 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -266,9 +266,10 @@ jobs:
actions: read
# Sign the provenance against the workflow's own OIDC identity.
id-token: write
- # The reusable workflow declares this for its own upload path; assets are attached by the
- # release job below, which keeps one job in charge of what the release contains.
- contents: write
+ # Read only. Inside the generator, contents: write belongs to its upload-assets job, which
+ # is guarded by `if: inputs.upload-assets` and therefore never runs here: the release job
+ # below attaches the provenance, so one job stays in charge of what the release contains.
+ contents: read
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
with:
base64-subjects: ${{ needs.build.outputs.hashes }}