feat(manifest): verify a v0.2 COSE manifest, not just a v0.1 document (#315) - #530
Merged
Conversation
…#315) Phase 4 of agent-manifest#243, split out as agent-manifest#315. The claim on record was that cmcp consumes the verifier through the published package and moves when v0.2 verification ships. The pin moved to agent-manifest>=0.11, which carries the COSE verifier, and nothing here had ever handed it a v0.2 manifest. It could not have worked. load_agent_manifest read JSON and _verify_with_sdk passed a dict, and from v0.2 the COSE_Sign1 structure is the signature (ADR-0011). A v0.2 document presented as a dict has no signature to appraise, so the SDK reported SIGNATURE_MISSING, which cmcp surfaced as "signature block is missing". That reads as a malformed manifest rather than a manifest supplied in the wrong form, and it is the error an operator following the v0.2 spec would have hit first. WHAT CHANGED load_agent_manifest_document() returns the decoded document together with the envelope bytes it arrived in, and the envelope is what reaches verify_manifest when there is one. Identity fields still come from the decoded document: what the envelope changes is which artifact the signature is checked over, not where identity lives. The file is sniffed rather than switched on its extension. A COSE envelope is CBOR and never parses as JSON, so trying JSON first and falling back is unambiguous, and an operator does not have to name the file correctly for the gateway to read it. A v0.2 payload supplied as bare JSON is now named precisely rather than inheriting the SDK's missing-signature message. load_agent_manifest() keeps its dict-returning signature. Callers that only read identity fields are unaffected; the ones that verify a signature use the new loader, which is the distinction the old single function hid. WHAT THIS PROVES test_cose_envelope_binds_end_to_end is the phase 4 evidence: a genuinely signed envelope, through cmcp's own binding path, to a bound session identity. Alongside it, an untrusted key is rejected, a tampered payload is rejected, and the v0.1 path binds exactly as before. The v0.1 arm uses the SDK's own signer rather than reconstructing a pre-image, so it cannot drift from the canonical byte sequence the verifier recomputes. Phase 5, the end date for issuing v0.1 manifests, is a maintainer decision and should not be set until a consumer verifies v0.2 in anger. This is the first half of that. VERIFICATION 10 new tests. tests/unit 1131 passed, 9 skipped. ruff, mypy and bandit clean. Refs agentrust-io/agent-manifest#315, agentrust-io/agent-manifest#243 Signed-off-by: Imran Siddique <imran.siddique@opaque.co> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
imran-siddique
enabled auto-merge (squash)
August 18, 2026 18:05
Member
Author
|
@AaronRoeF — all required checks are green. Could you take the maintainer approval pass when you have a moment? |
pforest
approved these changes
Aug 19, 2026
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.
Phase 4 of agentrust-io/agent-manifest#243, tracked as agentrust-io/agent-manifest#315.
cmcp could not verify a v0.2 manifest at all
The claim on record was that cmcp consumes the verifier through the published package and moves when v0.2 verification ships. The pin moved to
agent-manifest>=0.11, which carries the COSE verifier, and nothing here had ever handed it a v0.2 manifest.It could not have worked.
load_agent_manifestread JSON and_verify_with_sdkpassed a dict — and from v0.2 the COSE_Sign1 structure is the signature (ADR-0011). A v0.2 document presented as a dict has no signature to appraise, so the SDK reportedSIGNATURE_MISSINGand cmcp surfaced "signature block is missing". That reads as a malformed manifest rather than a manifest supplied in the wrong form, and it is the first error an operator following the v0.2 spec would have hit.What changed
load_agent_manifest_document()returns the decoded document and the envelope bytes it arrived in; the envelope is what reachesverify_manifestwhen there is one. Identity fields still come from the decoded document — what the envelope changes is which artifact the signature is checked over, not where identity lives.load_agent_manifest()keeps its dict-returning signature. Callers that only read identity fields are unaffected; the ones that verify a signature use the new loader — which is the distinction the single old function hid.What this proves
test_cose_envelope_binds_end_to_endis the phase 4 evidence: a genuinely signed envelope, through cmcp's own binding path, to a bound session identity. Alongside it: untrusted key rejected, tampered payload rejected, v0.1 path binding exactly as before. The v0.1 arm uses the SDK's own signer rather than reconstructing a pre-image, so it cannot drift from the canonical byte sequence the verifier recomputes.Phase 5 — the end date for issuing v0.1 manifests — is a maintainer decision and should not be set until a consumer verifies v0.2 in anger. This is the first half of that.
Verification
10 new tests.
tests/unit1131 passed, 9 skipped.ruff,mypy,banditclean.