diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..e4132ff
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,466 @@
+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
+ # 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 }}
+ 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