feat(scanner): add signed manifest checks#41
Merged
marmar9615-cloud merged 1 commit intomainfrom Apr 29, 2026
Merged
Conversation
Fourth implementation PR for v0.5.0 signed manifests. Wires the core verifyManifestSignature() helper from PR #39 into the scanner, emitting stable manifest.signature.* check IDs the CLI / MCP / Studio layers will branch on next. Local key-set input only — no remote /.well-known/agentbridge-keys.json fetch in this PR. Adds: - packages/scanner/src/tests/signature-checks.test.ts — 24 tests covering: * Default unsigned scanner output unchanged (regression). * scoreManifest with no signature options or empty signature options is observably identical to v0.4.x. * requireSignature mode emits manifest.signature.missing as error (deduction 15) on unsigned manifests. * Default mode emits manifest.signature.missing as info (deduction 0) when signature options are present but keySet is omitted — informational only. * Signed manifest with no keySet emits manifest.signature.unverified-no-key-set (info, no deduction) instead of `missing`. * Verified Ed25519 + ES256 manifests emit manifest.signature.verified. * Failure-mode mappings: tampered → invalid (25), unknown kid → unknown-kid (25), revoked kid → revoked-kid (30), expired → expired (20), issuer mismatch (incl. baseUrl origin binding) → issuer-mismatch (25), inverted-window or shape-bad signature → malformed (25), malformed key set → key-set-malformed (warning, deduction 0 — operator-side issue, manifest readiness unaffected), key-type mismatch (alg vs JWK) → key-type-mismatch (20). * Hygiene: scanner output never echoes public-key x/y bytes (pinned regression). * scanUrl integration: default invocation introduces no signature checks; with signature.keySet routes to verifier; with requireSignature=true emits the missing-signature error. * Round-trip the eddsa-valid + es256-valid + tampered-manifest vectors from spec/signing/test-vectors.json through scoreManifest. Modifies: - packages/scanner/src/score.ts — adds SignatureScoringOptions + ScoringOptions; scoreManifest accepts an optional second arg; scoreSignature helper runs verifyManifestSignature and maps every VerifyManifestSignatureFailure to a stable scanner check ID with severity + deduction per the design's §13.5 matrix. Defensive `never` exhaustiveness check on the failure mapping so a future verifier addition surfaces explicitly rather than silently dropping. Default behavior — no second arg — produces bit-for-bit identical output to v0.4.x for both unsigned and signed manifests. - packages/scanner/src/scanner.ts — adds optional `signature: SignatureScoringOptions` to ScanOptions; threads it into scoreManifest. No remote key-set fetch and no change to the existing manifest-fetch network path. - packages/scanner/README.md — adds a concise "Signed-manifest checks (v0.5.0, optional)" section with example, full check ID table (severity, deduction, when), and explicit notes on what is deferred (remote key fetch, MCP/CLI enforcement) and the additive-verification reminder. Does NOT (deliberately): - add MCP server enforcement - add CLI --require-signature - fetch remote key sets - require signatures by default - bump any package version - add any runtime dependency - publish, tag, or release Browser / computer-use status: - Not needed. The scanner is a Node library; no UI/browser surface changed. No Next.js builds touched. The CLI smoke validations (validate:examples, validate:mcp-config-examples) and the npm test / build / pack:dry-run are the appropriate smoke for this surface. Verified locally: - npm run typecheck:clean (clean) - npm test (409/409 across 25 files; was 385/24 on main = +24) - npm run build (all packages built) - npm run pack:dry-run (all six @marmarlabs/agentbridge-* OK at 0.4.0; scanner packed 16.3 → 24.1KB for the new module) - npm run validate:examples (✅ all examples validate) - npm run validate:mcp-config-examples (✅ all client-config examples validate) - npx vitest run packages/scanner/src/tests (44/44 — the existing 20 + 24 new) Tracking: #31 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2bd376b to
0a3120a
Compare
This was referenced Apr 29, 2026
marmar9615-cloud
added a commit
that referenced
this pull request
Apr 29, 2026
Fifth implementation PR for v0.5.0 signed manifests. Wires the core verifier from PR #39 + the scanner check IDs from PR #41 into operator-facing CLI commands. No MCP server enforcement, no remote /.well-known/agentbridge-keys.json fetch in this PR. Adds: - packages/cli/src/commands/verify.ts — `agentbridge verify <manifest> --keys <keyset.json>` with optional flags --expected-issuer, --now, --clock-skew-seconds, --json. Exit 0 on verified, exit 1 on any failure (carries the verifier's stable reason enum), exit 2 on usage error. --json mode emits a parseable structured outcome on stdout with no prose. - packages/cli/src/commands/keys.ts — `agentbridge keys generate --kid <id> --issuer <origin> --out-public <path> --out-private <path> [--alg EdDSA|ES256]`. Local-dev helper for bootstrapping a key set. Defaults to Ed25519. Refuses to silently discard freshly-generated material (--out-private is required). Writes the private key with mode 0o600 (owner-only) on POSIX. Never echoes the private `d` parameter to stdout/stderr; prints a sensitivity warning routed to stderr. - packages/cli/src/commands/key-loader.ts — small shared helper that loads a key set from disk and runs validateKeySet(). - packages/cli/src/tests/signature-cli.test.ts — 25 tests: * Default unsigned validation behavior unchanged. * --require-signature on an unsigned manifest exits 1. * --require-signature without --keys on a signed manifest exits 1 (refuses to silently report "verified" without verifying). * Ed25519 + ES256 happy paths via the spec/signing/test-vectors.json vectors. * Tampered, unknown-kid, expired, malformed-key-set, missing key-set path. * verify happy path, --json structure (success + failure), --expected-issuer mismatch, missing --keys (exit 2), missing manifest (exit 2). * Output never echoes the public-key x/y bytes (pinned for both success and failure paths). * keys generate: Ed25519 keypair, schema-valid public key set (no private `d`), POSIX mode 0o600 on the private file, no private bytes on stdout/stderr, sensitivity warning on stderr, --out-private required (exit 2 if omitted), non-canonical issuer rejected, freshly-generated key round-trips through Node crypto sign + agentbridge verify. Modifies: - packages/cli/src/commands/validate.ts — accepts new flags --keys, --require-signature, --expected-issuer, --now, --clock-skew-seconds. Default (no signature flags) behavior bit-identical to v0.4.x. With --keys, runs verifier and prints outcome. With --require-signature without --keys, refuses silent "verified" outcomes — surfaces missing-signature (unsigned) or no-key-set-supplied (signed but unverifiable) with exit 1. JSON mode includes a `signature` field next to the existing schema-validation result. Exports summarizeVerifyResult so the verify command shares the same output shape. - packages/cli/src/index.ts — dispatches `verify` and `keys` subcommands; threads new flags into runValidate / runVerify; expands --help with the new commands and a parseSkew helper. - packages/cli/README.md — adds concise sections for validate --keys / --require-signature, the verify command, the keys generate command (with explicit local-dev / KMS-in-production warnings), updated regression-tested-examples blurb, no-MCP-enforcement-yet reminder. Does NOT (deliberately): - add MCP server signature enforcement - fetch remote key sets - require signatures by default - bump any package version - add any runtime dependency - publish, tag, or release Browser / computer-use status: - Not needed. The CLI is a Node binary; no UI/browser surface changed. The Studio dashboard and demo-app weren't touched. Live CLI command smoke against the built dist (validate default, validate --keys, verify happy, verify --json, verify tampered → exit 1 + signature-invalid, keys generate with POSIX 0o600 + private bytes never on stdout) is the relevant live test for this surface and is documented in the PR body. Verified locally: - npm run typecheck:clean (clean) - npx vitest run packages/cli/src/tests/signature-cli.test.ts (25/25) - npx vitest run packages/cli/src/tests (17 + 11 + 11 + 5 + 25 = 69/69) - npm test (435/435 across 26 files; was 409/25 on main = +26) - npm run build (all packages built) - npm run pack:dry-run (all six @marmarlabs/agentbridge-* OK at 0.4.0; CLI packed 29.4 → 57.4KB for the new modules) - npm run validate:examples (✅ all examples validate) - npm run validate:mcp-config-examples (✅ all client-config examples validate) - live: validate (default + --keys), verify (happy + --json + tampered), keys generate (mode 600, no private bytes on stdout) — all pass against `node packages/cli/dist/bin.js`. Tracking: #31 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fourth implementation PR for v0.5.0 signed manifests. Wires
verifyManifestSignature()from PR #39 into the scanner, emittingstable
manifest.signature.*check IDs for every verifier outcome.Local key-set input only — no remote
/.well-known/agentbridge-keys.jsonfetch in this PR. Defaultscanner behavior is unchanged — unsigned manifests still score the
same and signed manifests trigger no signature check unless callers
opt in via the new
signatureoption.docs/designs/signed-manifests.md§13.546adc2cc0cd51d0bf7713Scanner signature surface
scoreManifest(manifest, options?)andscanUrl(url, options?)both accept an optional
signatureblock:Local-only (no remote fetch)
The scanner does not fetch the publisher's key set for you. Pass
the key set in via
signature.keySet. A runtime helper forremote fetch lands in a later v0.5.0 PR alongside MCP server
enforcement.
Check IDs added
All under category
safety. Stable identifiers — once shipped,renaming any of them is a major bump per
docs/v1-readiness.md§13.requireSignature)manifest.signature.verified(passed)manifest.signature.missingsignatureblockmanifest.signature.unverified-no-key-setkeySetsupplied — verification skippedmanifest.signature.malformedmanifest.signature.key-set-malformedmanifest.signature.unsupported-algorithmmanifest.signature.unknown-kidkidnot inkeys[]manifest.signature.revoked-kidkidinrevokedKids[]manifest.signature.issuer-mismatchmanifest.signature.before-signed-atnow<signedAt − skewmanifest.signature.expirednow>expiresAt + skewmanifest.signature.canonicalization-failedmanifest.signature.invalidmanifest.signature.key-type-mismatchTests added
packages/scanner/src/tests/signature-checks.test.ts— 24 tests:scanUrlintegration: default invocation introduces no signature checks; withkeySetruns verifier; withrequireSignatureemits the missing-signature error.eddsa-valid+es256-valid+tampered-manifestvectors fromspec/signing/test-vectors.jsonthrough the scanner.Confirmations
validate:examples, and by the existingpackages/scanner/src/tests/scanner.test.ts+scanner-fixtures.test.tscontinuing to pass.pack:dry-runconfirms all six@marmarlabs/agentbridge-*still at0.4.0.crypto+ existing@marmarlabs/agentbridge-coreimport.package-lock.jsonuntouched.packages/cli/*,examples/*(incl.examples/signed-manifest-basic/),scripts/*,apps/mcp-server/*,docs/releases/*, the root README, orCHANGELOG.md.Browser / computer-use status
Not needed. The scanner is a Node library; no UI / browser
surface changed. The Studio dashboard and demo-app weren't
touched. CLI smoke (
validate:examples,validate:mcp-config-examples) plus the test / build /pack:dry-run pipeline are the appropriate smoke for this
surface.
Commands run
Test plan
0.4.0.🤖 Generated with Claude Code