Agent Manifest COSE Envelope (manifest version 0.2)¶
| Status | Draft, normative for manifest version 0.2 |
| Date | 2026-07-27 |
| Decision | ADR-0011 |
| Tracking | Issue #243, phase 1 of 5 |
| Replaces | v0.1 specification section 3.6 (for version 0.2 manifests only) |
This document specifies the signature envelope for manifest version 0.2. It exists as a standalone document because the rest of the v0.2 specification has not been written yet; it will be folded into that specification as section 3.6 and this file will then be superseded. Until then it is the normative reference for the envelope.
Manifest version 0.1 is unaffected. A verifier MUST select the envelope by the manifest version field and MUST NOT apply this document to a version 0.1 manifest. Version 0.1 manifests continue to verify exactly as v0.1 section 3.6 specifies, for as long as the retention window requires.
1. Why COSE¶
ADR-0011 records the reasoning and it is not repeated here. The three properties this envelope exists to obtain:
- The signed bytes travel with the signature. COSE signs a payload as-is. A verifier never re-serializes anything to check a signature, which removes canonicalize-before-verify as an attack surface. RFC 8785 remains the producer-side determinism rule (ADR-0001) and the basis of the manifest hash bound into hardware, but it stops being an input to verification.
- The algorithm is covered by the signature.
alglives in the protected header. The downgrade a verifier had to defend against by cross-check in v0.1 (0.6.0 and 0.6.1 of the SDK) cannot be expressed here. - Post-signing additions have a defined home. COSE's unprotected header is exactly the place for data that attaches after signing. Three separate v0.1 mechanisms collapse into that one idea, as section 4 sets out.
2. Object structure¶
A signed Agent Manifest is one of:
COSE_Sign1(CBOR tag 18) when there is a single signature. This is the default and covers thestandardandpost-quantumprofiles with a single algorithm.COSE_Sign(CBOR tag 98) with exactly two signers when the issuer signs with both Ed25519 and ML-DSA-65 during the post-quantum transition.
A verifier MUST accept both tags. A verifier MUST reject an untagged COSE structure: the tag is what tells a relying party which verification procedure applies, and inferring it from the array shape is the kind of guess this envelope exists to eliminate.
2.1 Hybrid signatures¶
Hybrid is carried as a single COSE_Sign over one payload with two COSE_Signature entries, one per algorithm. It is not two COSE_Sign1 objects.
Each signature entry carries its own alg in its own protected header (-19 for Ed25519, -49 for ML-DSA-65) and its own kid. Both entries cover the identical payload bytes, which the structure guarantees rather than an application rule.
A verifier operating under a policy that requires post-quantum protection MUST verify both entries and MUST reject the manifest if either fails. A verifier that cannot perform ML-DSA-65 MUST return UNVERIFIABLE and MUST NOT fall back to the Ed25519 entry alone, which would be a downgrade (v0.1 section 4.2, carried forward).
3. Protected header¶
The protected header is covered by the signature. For COSE_Sign1 these parameters appear in the object's protected header; for COSE_Sign typ and content type appear in the body protected header, while alg and kid appear in each signature's protected header.
| Parameter | Label | Value | Presence |
|---|---|---|---|
alg | 1 | -19 (Ed25519) or -49 (ML-DSA-65) | REQUIRED |
kid | 4 | Key identifier, byte string. The SHA-256 of the public key bytes, as in v0.1 | REQUIRED |
content type | 3 | application/agent-manifest+json | REQUIRED |
typ | 16 | application/agent-manifest+cose | REQUIRED |
alg values are the IANA-registered COSE code points: Ed25519 from RFC 9864, and ML-DSA-65 from RFC 9964, which also registers the AKP key type (COSE key type 7). No provisional or draft code points are used.
A producer MUST sign with -19 and MUST NOT sign with -8. A verifier MUST accept -8 on an existing manifest and MUST treat it as Ed25519 (ADR-0014). RFC 9864 (Standards Track, October 2025) deprecated the polymorphic EdDSA identifier -8, which named a family and left the curve to be inferred from the key, and registered fully-specified identifiers in its place. Inferring an algorithm from a key is the same class of ambiguity that ADR-0011 gives as a reason to move off the v0.1 envelope, so this profile takes the fully-specified identifier. -8 remains verifiable because manifests are audit records with a retention window that outlives the identifier they were signed under.
-8 and -19 name one algorithm, not two. Anything reasoning about which algorithm signed - the crypto_profile check of section 6, and the two-signer rule of section 2.1 - MUST compare algorithms rather than code points, so that a COSE_Sign carrying one entry of each is rejected as a single algorithm signed twice rather than accepted as a hybrid signature.
typ (RFC 9596) declares the type of the complete COSE object; content type declares the type of the payload. They are deliberately different values: the object is CBOR, the payload is JSON. A verifier MUST reject an object whose typ is absent or is any value other than application/agent-manifest+cose, which prevents a manifest signature from being reinterpreted as a signature over some other kind of document.
A verifier MUST reject a protected header containing a crit parameter naming anything it does not understand. A verifier MUST NOT accept alg from an unprotected header under any circumstances.
4. Payload, and what attaches after signing¶
The payload is the RFC 8785 canonical JSON serialization of the manifest document, as a byte string, carried inline. Detached payloads are not used: a manifest is a few kilobytes and is not secret, so detaching would trade a self-contained artifact for a fetch that can fail or be substituted. (SCITT permits detached payloads for large or sensitive statements; this profile does not need that latitude.)
The payload contains every manifest field that is settled at signing time. It does NOT contain signature, which no longer exists as a manifest field: the COSE structure is the signature.
Three v0.1 mechanisms disappear, replaced by the unprotected header:
| v0.1 mechanism | v0.2 |
|---|---|
The fixed signed_fields list (v0.1 section 3.6) and its coverage table | Deleted. The payload is what is signed. There is no list to keep in sync and no field that is silently outside the signature. |
hitl_record.approvals normalized to [] in the pre-image so approvals could attach later | Deleted. The HITL requirement stays in the signed payload; approvals attach in the unprotected header. |
transparency_log_entry as an unsigned top-level field with a five-step ordering rule | Deleted. The receipt attaches in the unprotected header per SCITT. |
4.1 Unprotected header parameters¶
| Parameter | Label | Contents | Presence |
|---|---|---|---|
receipts | 394 | SCITT receipts (RFC 9942), an array. Registering a signed manifest with a Transparency Service yields a receipt; a manifest carrying one is a Transparent Statement in SCITT terms | REQUIRED for production (v0.1 section 3.6 rule carried forward) |
agent-manifest-attestation | (string label, pending IANA) | The hardware attestation block, which is produced after signing and is therefore not covered by the issuer signature. Same semantics as the v0.1 attestation field | REQUIRED for Level 1 and above |
agent-manifest-approvals | (string label, pending IANA) | HITL approval records, each independently authenticated by its own approval_signature per v0.1 section 3.5 | REQUIRED when the signed payload's hitl_record.required is true |
receipts uses the label RFC 9943 assigns. The two Agent Manifest parameters use string labels until integer labels are requested from IANA in the v1.0 work; COSE permits string labels, so this is valid rather than a placeholder that must be swapped before shipping.
Nothing in the unprotected header is covered by the issuer signature, which is precisely why these three things live there. Each carries its own integrity story: a receipt is signed by the Transparency Service, an attestation report by the TEE, and an approval by its approver.
5. What hardware attestation binds¶
sha256 of the payload bytes: the canonical JSON of the manifest document, exactly the bytes carried in the COSE payload.
This is a simplification of v0.1, where the attestation bound a hash of the manifest with attestation, signature, and transparency_log_entry excluded, and every future top-level field had to be classified. Here the payload is by construction the settled part of the manifest, so there is nothing to exclude and nothing to keep in sync.
The binding is carried in the platform's caller-supplied field as v0.1 section 3.3.1 already specifies per platform (HOST_DATA for SEV-SNP boot binding, REPORT_DATA and REPORTDATA for the runtime freshness proofs of section 3.3.2). Those sections are unchanged.
6. Verification procedure¶
A verifier MUST perform these steps in order and MUST fail closed at the first failure.
- Parse the CBOR. Reject anything that is not a tagged
COSE_Sign1(18) orCOSE_Sign(98). - Read the protected header. Reject an absent or unexpected
typ; reject an unknowncritentry; reject an absentalg. - Extract the payload and parse it as JSON. Reject a
versionthis verifier does not support, returningINCOMPATIBLE_VERSION(v0.1 section 2.4 semantics carried forward). Route a0.1payload to the v0.1 envelope rules; this document applies to0.2. - Check the signed
crypto_profilein the payload against thealgin the protected header. Reject a signature weaker than the declared profile requires: apost-quantumprofile with only Ed25519 (-19, or-8on an existing manifest) is a downgrade. A signature stronger than the profile requires is permitted. This is the v0.1 section 4.2 rule, retained because the profile is a claim about posture that the algorithm alone does not express. - Verify the signature over the COSE
Sig_structure. ForCOSE_Sign, verify every signature entry the governing policy requires. - Return
UNVERIFIABLE, notMISMATCH, if the algorithm is registered but this verifier cannot perform it. The verifier has established nothing about a manifest that may be entirely valid (v0.1 section 4.2, as amended). - Only then evaluate the unprotected header: receipt inclusion proof, attestation report, approvals. A failure in any of these is reported against that element, not as a signature failure.
Step 7 being last is normative. An unprotected header is attacker-malleable by definition, so nothing in it may influence whether the signature verifies.
7. Media types¶
Two registrations are needed. Both use the agent-manifest name in the standards tree, filed as part of the v1.0 standardization path (v0.1 section 10.3).
| Media type | Applies to |
|---|---|
application/agent-manifest+json | The manifest document: the canonical JSON payload, and the natural type for a manifest at rest or over HTTP before signing |
application/agent-manifest+cose | The signed object: a COSE_Sign1 or COSE_Sign carrying a manifest payload |
Until registration completes, implementations MUST use these exact strings and documentation MUST state that registration is pending. A verifier MUST NOT accept a vendor-tree alias: two valid typ values for one object type is the ambiguity typ exists to remove.
8. Example (CBOR diagnostic notation)¶
A Level 0 manifest signed with Ed25519, before registration with a Transparency Service:
18( / COSE_Sign1 /
[
h'a4012703...', / protected: {1: -8, 4: h'...', 3: "application/agent-manifest+json",
16: "application/agent-manifest+cose"} /
{}, / unprotected: empty until a receipt attaches /
h'7b224063...', / payload: RFC 8785 canonical JSON of the manifest /
h'd28c1f5e...' / signature /
]
)
The same manifest after registration and hardware attestation, showing only the unprotected header:
{
394: [h'd284...'], / receipts (RFC 9942) /
"agent-manifest-attestation": { / TEE report, signed by the platform /
"platform": "amd-sev-snp",
"manifest_hash_in_report": "sha256:...",
"report_uri": "..."
}
},
9. Open items for phase 2¶
These are implementation decisions, not envelope decisions, and are listed so phase 2 does not rediscover them. The first two are now settled in ADR-0013:
- ~~Which CBOR and COSE library the Python SDK takes.~~ Decided:
cbor2only, with the COSE structures built in the SDK. No COSE library is taken, so the crypto surface stayscryptographyplus optionalpyoqs. - ~~Whether the SDK emits
COSE_Sign1with a zero-length unprotected header map or omits it before registration.~~ Decided: always a zero-length map. Only one of the two is a validCOSE_Sign1— omitting the element yields a three-element array, which RFC 9052 section 4.2 does not define. Pinned byte-for-byte inAM-VEC-COSE-001(unprotected_hexisa0). AM-VEC-*conformance vectors for negative cases only this envelope can express: a tampered protected header, analgsubstituted between the protected and unprotected headers, atypmismatch, and an unprotected header injected before signature verification.- Whether the
agent-manifest-attestationandagent-manifest-approvalsstring labels are worth converting to IANA integer labels before v1.0, which is a wire-size argument only.
10. References¶
- RFC 9052, RFC 9053 - COSE structures and algorithms, including the now-deprecated polymorphic
EdDSA(-8) - RFC 9864 - Fully-specified algorithms for JOSE and COSE, Standards Track, October 2025. Deprecates
EdDSA(-8); registers Ed25519 =-19and Ed448 =-53. See ADR-0014 - RFC 9596 - COSE
typheader parameter (label 16), Standards Track, June 2024 - RFC 9964 - ML-DSA for JOSE and COSE, Standards Track, May 2026. ML-DSA-65 =
alg-49, AKP key type7 - RFC 9943 - SCITT architecture. Signed Statements are COSE_Sign1;
receiptsis unprotected header label 394 - RFC 9942 - COSE Receipts
- RFC 8785 - JSON Canonicalization Scheme, the payload serialization
- ADR-0001 (RFC 8785), ADR-0005 as amended (profiles and algorithms), ADR-0011 (this decision)
- v0.1 specification sections 2.4, 3.3, 3.5, 3.6, 4.2, 10.3, 10.5