Skip to content

feat(manifest): verify a v0.2 COSE manifest, not just a v0.1 document (#315) - #530

Merged
imran-siddique merged 2 commits into
mainfrom
agent/cose-consumer-315
Aug 19, 2026
Merged

feat(manifest): verify a v0.2 COSE manifest, not just a v0.1 document (#315)#530
imran-siddique merged 2 commits into
mainfrom
agent/cose-consumer-315

Conversation

@imran-siddique

Copy link
Copy Markdown
Member

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_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 and 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 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, not switched on its extension. A COSE envelope is CBOR and never parses as JSON, so JSON-first with a fallback 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 instead of 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 single old 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: 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/unit 1131 passed, 9 skipped. ruff, mypy, bandit clean.

…#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 imran-siddique added the cosai-ws4 Named in the CoSAI WS4 Phase 1 review (ws4 #149) as a review target label Aug 18, 2026
@imran-siddique imran-siddique self-assigned this Aug 18, 2026
@imran-siddique
imran-siddique enabled auto-merge (squash) August 18, 2026 18:05
@imran-siddique
imran-siddique requested review from a team and removed request for a team August 18, 2026 18:58
@imran-siddique

Copy link
Copy Markdown
Member Author

@AaronRoeF — all required checks are green. Could you take the maintainer approval pass when you have a moment?

@imran-siddique
imran-siddique merged commit f3b31a6 into main Aug 19, 2026
13 checks passed
@imran-siddique
imran-siddique deleted the agent/cose-consumer-315 branch August 19, 2026 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cosai-ws4 Named in the CoSAI WS4 Phase 1 review (ws4 #149) as a review target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants