Skip to content

feat(claude-code): persistent signing key makes the manifest verifiable#41

Merged
imran-siddique merged 1 commit into
mainfrom
feat/claude-code-persistent-signing-key
Jul 16, 2026
Merged

feat(claude-code): persistent signing key makes the manifest verifiable#41
imran-siddique merged 1 commit into
mainfrom
feat/claude-code-persistent-signing-key

Conversation

@imran-siddique

Copy link
Copy Markdown
Contributor

Found while doing a full end-to-end run of the plugin as a new user would (/manifest + /trace lifecycle in a throwaway home).

The gap

The signed Agent Manifest could not be verified by anyone. sign_all minted a fresh keypair per run with generate_ed25519() and discarded it; manifest.json stored only key_id (a hash of the public key), never the key. The signature was real but unverifiable after the process exited, which contradicts the README's "shareable proof a third party can verify." (TRACE was fine: trace.json embeds cnf.jwk and verifies standalone.)

The fix

  • Persistent key. One Ed25519 key at ~/.claude/agentrust/signing_key.json, generated once and reused every run (reconstructed from the stored seed via cryptography), best-effort owner-only perms. Records now carry a stable signing identity instead of a throwaway key.
  • Published public key. Each run writes verification_key.json ({key_id, public_key_b64url}) beside the records. The private half never leaves the machine.
  • Docs. Corrected the README claim and added the exact manifest-verification recipe (load verification_key.json into trusted_keys); /trace command doc updated; .gitignored the new artifacts.

Validation

  • Key is stable across runs (same key_id).
  • A third party with only verification_key.json -> OverallResult.VALID, signature_verified True.
  • Tamper agent_id / an artifact hash, or use a wrong key -> MISMATCH, signature_verified False.
  • TRACE suite still PASS (8/8); full /manifest + /trace E2E still green.
  • 2 crypto-guarded regression tests added (importorskip); 15 pass both with and without the crypto packages. ruff clean.

Note

Trace records still use a per-run key from agentrust_trace (self-verifiable via cnf.jwk). Reusing the one persistent identity for TRACE too is a reasonable follow-up but out of scope here. Also flagged separately: the plugin's pytest suite isn't run by CI (validate.yml only checks integration.yaml schemas) — worth wiring up.

🤖 Generated with Claude Code

E2E testing showed the signed Agent Manifest could not be verified by anyone:
sign_all minted a fresh keypair per run with generate_ed25519() and discarded
it, and the manifest carried only key_id (a hash of the public key), never the
key itself. The signature was real but unverifiable after the process exited,
contradicting the README's "shareable proof a third party can verify". (TRACE
was unaffected: trace.json embeds cnf.jwk and verifies standalone.)

- Persist one Ed25519 key at ~/.claude/agentrust/signing_key.json, generated
  once and reused every run (reconstructed via cryptography from the stored
  seed); best-effort owner-only permissions. Records now carry a stable signing
  identity instead of a throwaway key.
- Publish the public half beside each record as verification_key.json
  ({key_id, public_key_b64url}); the private key never leaves the machine.
- README + /trace doc: correct the claim and show the exact manifest
  verification recipe (load verification_key.json into trusted_keys).
- gitignore verification_key.json and signing_key.json.

Verified: key is stable across runs; a third party with only verification_key.json
gets VALID / signature_verified True; any tamper (agent_id, artifact hash) or a
wrong key -> MISMATCH. Added 2 crypto-guarded regression tests (importorskip);
15 pass with and without the crypto packages. ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@imran-siddique imran-siddique merged commit c0bb23f into main Jul 16, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant