Changelog¶
[Unreleased]¶
Security¶
-
[SDK] TDX quote signature parsing rejects trailing bytes inside the declared signature and QE certification containers, after the nested QE data or PCK certificate chain. Existing padded hardware capture buffers remain supported.
-
[Spec] The transport row in section 4.1 no longer recommends TLS 1.3 with the attestation extensions of draft-fossati-tls-attestation-08. That draft carries evidence inside the handshake and binds it to the ephemeral key only, so a relay holding any attested ephemeral key can divert the session (CVE-2026-33697). Implementations MUST NOT treat intra-handshake evidence as a session binding. A value released on attestation MUST be sealed to a transport key generated inside the attested environment, whose private key never leaves it and whose public key the evidence binds alongside a fresh nonce (report data where the platform exposes it, otherwise the enclosing quote). Sections 4.2 and 9.3 no longer point at the removed mechanism, and 9.3 no longer claims TLS alone satisfies HIPAA transmission security.
Removed¶
- [SDK]
OPAQUEProvideris removed from the public API, together with theopaquechoice formanifest attest --provider, theOPAQUE_ATTESTATION_URLcheck inselect_provider(), and theopaqueplatform label. The provider never worked: it raisedAttestationUnavailableErroron construction. This is a breaking change for code that imports the name. TheAttestationProviderinterface and the TPM, SEV-SNP, TDX, Azure CVM and software providers are unchanged.
Changed¶
-
[Docs/tests] Reconcile the five reference conformance modules with pytest collection (233 parametrized cases), add a generated executable node-ID inventory and CI drift check, and replace unresolved prose IDs in contributor templates and ADRs with runnable references. Stabilize two time-dependent parameter IDs. Collection counts do not establish a passing run, exhaustive requirement coverage or a level-specific hardware acceptance result.
-
[SDK] Issuer key authorization now follows spec section 5.3.3 on both envelopes. This is a breaking change for any caller that sets
trusted_keyswithouttrusted_key_issuers. - With
trusted_key_issuers=Nonethe issuer check cannot be completed, so the signature is not verified and the result isUNVERIFIABLEwith a warning naming section 5.3.3. It used to skip the check and could returnVALIDwith no issuer authorization at all. An authorization failure is stillMISMATCH, so the two conditions stay distinguishable as section 5.3.3 requires. - On a v0.2 COSE envelope, key resolution and issuer authorization run before the envelope signature is checked. An unauthorized key is now refused with
signature_verified: false(AM-VEC-COSE-009); before, the signature was verified first and the flag readtrue. - A v0.1 manifest without
issuerstill passes schema validation but can no longer reachVALID: there is no declared issuer to authorize the key for. manifest verifygains--authorized-issuer(repeatable), the issuer the--public-keykey may sign for. Without it the CLI result isUNVERIFIABLE.- Every conformance vector context that expects a verified signature now carries
trusted_key_issuers. New vectors AM-VEC-025 (v0.1) and AM-VEC-COSE-016 (v0.2) pin the no-policy case asUNVERIFIABLE.
Both divergences from the section 5.3.3 text were found by @litzki-systems while writing the issuer key authorization guide (#493).
-
[Spec] Section 6.3.2 separates naming a manifest from binding it. A producer that claims a binding MUST carry a digest of the manifest covered by the TRACE record's signature, and a verifier MUST NOT report the producer's
manifest_verification_resultas its own appraisal; an unresolvable or undigested manifest isUNVERIFIABLE. TRACE defines the carrying field. The result field stays required, so the SDK's envelope validator is unchanged. Raised on agentrust-io/trace-spec#483. -
[Spec] The MCP
initializeexample in section 6.3.1 uses the namespaced keycom.agentrust-io.agentManifestin place ofco.opaque.agentManifest, and advertises"version": "0.2". The section 3.1 schema example now saysversionis'0.2', matching the field table. Section 5.1.1 names the second hosting model "service-hosted mode". Spec text, examples and test fixtures use neutral provider and model names. -
[Docs] ADR-0008 carries a dated note that the spec section 8.1 table is authoritative and that no conformance level names a vendor runtime.
Fixed¶
-
[SDK] Reject RFC 7517 / RFC 7518 private or symmetric JWK parameters (
d,p,q,dp,dq,qi,oth,k), non-minimal leading-0x00Base64urlUIntencodings, duplicate JSON object keys, and invalid RSA public parameters (n < 3,e < 3, or even values) on AzureHCLAkPubruntime data; and bound delegation and HITL integer fields (hop,max_delegation_depth,ttl_seconds, andapproval_duration_seconds) by the RFC 8785 safe-integer range (2**53 - 1) (#526). -
[Spec/SDK]
poisoning_scancarried aresultbut named nothing about which corpus it was computed over: acleanorflaggedscan verified unchanged next to anyrag_corpus.merkle_root, so a result produced over one corpus could be presented against a different, unscanned one. Section 3.2.5 now requirespoisoning_scan.subject_digestto equalrag_corpus.merkle_rootbefore the scan'sresultis interpreted at all: a present-but-differentsubject_digestfails at every conformance level regardless ofresult(includingnot-scanned), and an absent one fails at Level 1+ wheneverresultiscleanorflagged. Level 0 still permits an absentsubject_digest. The SDK verifier checks this binding first and does not readclean/flaggedas a statement about the corpus once it fails. Closes #472. -
[Spec] Section 2.3 said a field the spec requires to be
null(for examplemodel_hashwith anapideployment) stays in the canonical form asnull. The SDK omits it, and every signed v0.1 vector with an API model is signed over the form without it: AM-VEC-001 verifies over that pre-image and fails over one that keeps"model_hash":null. Sections 2.3 and 4.3 now say null-valued manifest fields, required-null ones included, are omitted from the manifest pre-images as a step before RFC 8785. Other JSON inputs are outside the rule. Reported in #482. -
[SDK] Eleven hash/key-id checks used
==, orcompare_digest()with no type/charset guard, instead of a safe constant-time compare:verify_transparency_log_entry(),TPMProvider/SoftwareProvider.verify_manifest_in_report(), the external-report fallback inSEVSNPProvider/TDXProvider, the REPORT_DATA check inverify_attestation_chain(), and in_verify.pythe attestationmanifest_hash_in_reportcheck,_split_hybrid_public_key(), thereport_data_hashcheck behindverify_runtime_report()(verify_runtime_quote()), the decision-traceaudit_chain_rootcheck, and the generic artifact_check()used forsystem_prompt,policy_bundle,model_identity, etc. Same fix as #450 for_revocation.py'ssigner_key_id: guard non-str/non-ASCII input, sincecompare_digest()raises on those instead of returningFalse(SEC-TRANS-01). Without the guard, a malformed manifest, a non-ASCIImodel_identity.version(a plain string, not a hash), or a caller's own non-ASCIIVerificationContext.audit_chain_root/model_versioncould crash verification withTypeErrorinstead of getting a normalMISMATCH. -
[SDK] A present
attestationthat wasnull,{}, any other non-object value, or missing the REQUIREDmanifest_hash_in_reportfield (or that field present but falsy), was silently treated as if attestation were absent instead of theMISMATCHspec 3.3 requires.attestationMUST be omitted, notnull, when there's none - only the field being entirely absent is still a policy question governed byenforce_attestation. Fixed on both the raw-manifest and COSE-envelope paths; on COSE, two newCoseVerificationproperties (attestation_label_present,attestation_raw) tell an absent attestation label apart from one present holding a bad value, which.attestationalone couldn't. -
[DOCS] Correct the Art. 22 attribution in the section 9.1 EU AI Act table and the Annex III point 5 sub-points in section 9.1.1 (#456). Art. 22 of Regulation (EU) 2024/1689 concerns authorised representatives of providers of high-risk AI systems; the removed row described Art. 22 of Regulation (EU) 2016/679, which section 9.3 already documents, so the row is replaced by a reference to that section and section 9.3 is left unchanged. Annex III point 5(b) covers creditworthiness and excludes systems used for the purpose of detecting financial fraud, and point 5(c) covers risk assessment and pricing for life and health insurance. Editorial only; no field, schema, conformance level or verifier behaviour changes. Reported by @ioanavalea; scope set by @imran-siddique on #456.
[0.15.0] - 2026-09-30¶
Security¶
- [SDK]
verify_attestation_chain()refuses debug guests. The SEV-SNP path never read the signed guest policy and the TDX path never parsed TDATTRIBUTES, so a debug-enabled guest with an allow-listed measurement returnedpassed=True. A debug guest's memory is readable and writable by the host, so its REPORT_DATA and the code that chose it are the host's to set. The verdict now fails when SNP guest policy DEBUG (bit 19, AMD 56860) or TDXTDATTRIBUTES.DEBUG(bit 0, TD report body offset 120) is set, read from the signed bytes after the signature verifies. SNP reports must also carry VMPL 0, the level both of this package's SNP producers request at (SEVSNPProviderthrough configfs-TSM, and the Azure paravisor).verify_runtime_report()andverify_runtime_freshness()apply the same checks with no opt-in.ChainVerificationResultgainsdebugandvmpl, read from the signed report (Nonewithout a verified signature, and for platforms with no such field).verify_attestation_chain(allow_debug=True)accepts a debug guest for development and records it inreasons; it does not relax VMPL.SnpReport.debug,TdxQuote.td_attributesandTdxQuote.debugexpose the parsed state. Behaviour change: a debug guest, or an SNP report at VMPL 1 to 3, that previously verified now fails.
Added¶
- [SDK] Experimental, opt-in
evidence-requirements-experimental-v1profile. A v0.2 manifest that declares it may carry a signedevidence_requirementsblock (components, required relationships, combination policy reference), andagent_manifest.evidence_requirements.verify_evidence_manifest()returns it only afterverify_manifest()isVALID. Without the profile the block is still rejected as an unknown field, so existing manifests verify as before. Under the profile every 0.14.0 check still applies, including the signedaudit_key_sealedrule underenforce_attestation. Not part of the specification; seedocs/evidence-requirements-experimental.md. Consumer: agentrust-io/trace-spec#439.
Fixed¶
- [SDK]
attach_receipt()corrupted or crashed when the envelope'sreceiptsheader (label 394) wasn't already an array - a map became its keys, a string its characters, andnull/0/false/""were silently replaced with[].attach_approvals()was already fixed for the same issue in 0.13.0;attach_receipt()now works the same way: a non-array existing value raisesCoseStructureErrorinstead of being coerced or overwritten, andreceiptitself must bebytesorbytearray. To replace the array, useattach_unprotected(envelope, HDR_RECEIPTS, receipts). Behaviour change: a bad receipt, or a non-arrayreceiptsvalue, now raises instead of being silently coerced.
[0.14.0] - 2026-09-30¶
Security¶
Three privately reported advisories in the hardware-evidence verifiers. Each reached a passing verdict from a value nothing signs.
-
[SDK]
verify_attestation_chain()reads REPORT_DATA and the measurement from the signed report (GHSA-cf88-228w-w58h). The SNP, TDX and TPM signatures were checked over the quote bytes, and the manifest binding and measurement allow-list were then evaluated againstreport.raw, a dict nothing signs. A genuine report for one manifest and measurement verified as binding any other. Once the signature verifies, both fields now come from the signed bytes (SNPREPORT_DATA/MEASUREMENT, TDXREPORTDATA/MRTD, TPMextraData/PCR digest), and arawcopy that disagrees with them fails the check.rawis still read when the signature did not verify, for the diagnostic fields only;passedcannot be true in that case. -
[SDK] The SEV-SNP chain is pinned to AMD's root by default (GHSA-cf88-228w-w58h).
verify_vcek_chain()andverify_attestation_chain()tooktrusted_ark_der=Noneto mean no pin, and no AMD root shipped, so a VCEK/ASK/ARK chain built from fresh keys verified. The ARKs for Milan, Genoa and Turin, as served bykdsintf.amd.com/vcek/v1/<product>/cert_chain, are now embedded with the SHA-256 of each DER (AMD_ARK_SHA256), and the chain's ARK must match one of them.trusted_ark_derstill overrides the set. Behaviour change: a chain under any other root, including a test chain, now fails unless its root is passed astrusted_ark_der. -
[SDK]
verify_runtime_report()reads the nonce from the verified quote (GHSA-32q9-m5rc-rp3w). It compared the provider's ownreport_data_hashwith the same derivation and never opened the quote, so a replayed quote, or no quote, verified as fresh. It now verifies the quote (new keyword arguments carry the SNP VCEK material, a TDX root override, or the TPM quote and AK chain) and requires the signedREPORT_DATAorextraDatato carrysha256(nonce || context_hash_bytes).verify_runtime_freshness()takes the same arguments.AzureCVMProvider.attest_runtime_state()now includesruntime_data_hexinrawso the vTPM AK can be tied to the SNP report. Behaviour change: without verification material, or for a platform with no hardware quote (software), the result is nowFalse. -
[SDK]
enforce_attestationacceptsaudit_key_sealedonly when it is signed (GHSA-489r-r3g9-g24r).attestation.audit_key_sealedsits outside the signing pre-image and the attestation-bound hash, so it could be flipped totrueafter signing, undoing the GHSA-mqqg-9mpc-mpg7 enforcement. Underenforce_attestationthe signed artifact #7 copy,artifacts.decision_trace.audit_key_sealed(spec 3.2.7), must now also betrue. The signing pre-image is unchanged, so every existing signature still verifies. Behaviour change: underenforce_attestation, a manifest that sets the attestation-block flag without a signed sealeddecision_tracebinding returnsATTESTATION_UNAVAILABLE.
Fixed¶
-
[SPEC] Three statements corrected, raised on cosai-oasis/ws4-secure-design-agentic-systems#149. The subtitle, abstract and section 1.1 called the manifest an identity standard; it is a declaration of deployment content and references identity credentials rather than replacing them. Section 3.2.8 required
container_image_digestto match the TEE hardware measurement, which covers firmware and the initial guest image, not a container: the digest is now hardware-bound only through a measured runtime layer that extends it into an RTMR or vTPM PCR, and a verifier MUST NOT report it as hardware-bound otherwise. The NVIDIA Blackwell profile had the runtime write a custom SPDM measurement index; SPDM measurements are produced by the responder, so the manifest is bound through the host CPU profile instead. -
[SPEC] Section 3.2.3 requires a two-tool
catalog_hashtest vector in Appendix D, and Appendix D held only the RFC 8785 vector. Appendix D now carries both: D.1 is the existing canonical JSON vector, D.2 the two-tool catalog with its leaf pre-images, root, reordered-input control and empty root. The values are the onespython/tests/test_merkle.pyalready asserts. Reported on #340.
[0.13.1] - 2026-09-26¶
Fixed¶
-
[SDK]
verify_evidence_pack()returnedVERIFIEDwhatever the embedded section 5.2verification_resultsaid. Only its enum membership was checked, so a pack carryingREVOKED,EXPIREDorMISMATCH, or a result naming a different manifest, verified the same as one carryingVALID, and itsverification_signaturewas never checked. A non-VALIDverdict or amanifest_idthat differs from the pack's manifest now fails the pack, and a result withoutmanifest_idisMALFORMED. New keyword argumentsresult_key_idandresult_algorithmappraiseverification_signatureover the newverification_result_pre_image(); whenresult_key_idis omitted the result carriesverification_result_signature_verified=Falseand averification_result_signature_not_appraisedwarning.signature_verifiedstill describes the outerpack_signatureonly (#463). Behaviour change: a pack whose embedded result isREVOKED,EXPIRED,MISMATCHor any other non-VALIDvalue now returnsFAILED. Callers that accepted such packs asVERIFIEDunder 0.13.0 will see them rejected. -
[SDK]
verify_evidence_pack()andverify_trace_envelope()raisedTypeErrororValueErroron some untrusted input instead of returning a status: a non-stringverification_result.resultorpack_signature.key_id, and any value RFC 8785 cannot represent (NaN, Infinity, a lone surrogate, an integer beyond 2^53, nesting deeper than 64). All of these now returnMALFORMED(#463).
Changed¶
- [CI] CodeQL now analyses every pull request and push to
main; thepython/**path filter and the Dependabot skip are gone (#463). Workflow write permissions incodeql.ymlanddocs.ymlare scoped to the job that uses them (#464). A ClusterFuzzLite targetfuzz_trace.pycoversverify_evidence_pack()andverify_trace_envelope()(#463).
[0.13.0] - 2026-09-25¶
Added¶
-
[SDK] Separate
VerificationResult.hitl_admissibilityreporting for issue #348. Passing approval checks leaves present applicabilityUNDECIDABLEbecause the verifier cannot establish current approver standing or successor state. Results distinguish approval not required, failed approval checks, and incomplete verification. Existing HITL enforcement and overall verdicts are unchanged. -
[SPEC] Section 6.2.1 records the enforcement mode vocabulary crosswalk (issue #344). The manifest names the three policy enforcement states
enforce,advisoryandaudit-only, the cMCP runtime names themenforcing,advisoryandsilent, and the TRACE claim schema uses a third set,enforce,advisoryandsilent. Section 6.2 requires the manifest and the attested runtime mode to match but never said which name denoted which state, so the only written statement of the correspondence sat inside a consumer (agentrust-io/cmcp#584). The new subsection is informative, carries no RFC 2119 keyword, and adds no requirement beyond the existing row. It records the correspondence without asserting thatsilentandaudit-onlyare the same state. Reported by Imran Siddique.
Fixed¶
-
[SDK]
verify_deltaandverify_continuityparsed a checkpoint'smemory_root/audit_chain_rootwithpartition(":")+fromhex()instead of theHashValueschema's own validator, so a root that never went through pydantic (both fields are plainstr, populated from runtime-observed checkpoints) could be malformed and still verify. Uppercase hex invalid per the schema, sincebytes.fromhexdoesn't care about case let a checkpoint advance verify as accepted/continuous when it should have failed closed; confirmed by reproduction in both functions. Also hardened:verify_consistencynow validates its own inputs (a direct caller had no shape checks at all, unlikeverify_consistency_append) and applies the same proof-length bound, andverify_continuitynow rejects malformed/wrong-typed checkpoints instead of raising, matchingverify_delta's existing behavior. -
[SECURITY][SDK]
canonicalize()is now RFC 8785 conformant (#404, closes #322). Checked against therfc8785reference implementation on 30,000 randomised documents. Three divergences are gone: strings are no longer NFC normalized (two distinct strings could share one signature, and two sibling keys that normalized alike emitted invalid JSON that dropped a signed field); the escape set is now ECMAScript QuoteJSONString (U+2028, U+2029 and U+007F to U+009F are no longer escaped); integers above 253 - 1 are refused. Compatibility:** a document containing non-NFC text, those characters, or an integer outside the safe range now canonicalizes differently or is refused, so signatures produced by 0.12.0 over such documents do not verify. Documents without them produce the same bytes as before. -
[SDK]
verify_hitl_approval()raisedAttributeError,KeyErrororTypeErroron malformed approvals (non-object approval or scope, missing fields, non-numeric duration, non-string signature). Every shape is now checked first and rejected withValueError(#378, fixes #360). -
[LICENSE]
LICENSEis the canonical Apache-2.0 text again; the altered copy failed licence detection (#406). No licence terms changed. -
[SDK]
build_catalog_tree()built each tool's Merkle leaf from the raw digest bytes ofschema_hash/description_hash, dropping thesha256:/shake256:algorithm prefix. Two hashes with the same digest but different algorithm labels hashed to the same leaf, socatalog_hashnever actually bound which algorithm a tool's hash was computed under only the raw bytes. Within a catalog tree, a tool'sschema_hashanddescription_hashalgorithms must match the tree's own algorithm (spec Section 3.1; profile tables in 4.1/4.2), sobuild_catalog_tree()now rejects any tool where they don't.verify_manifest()already treats aValueErrorfrom this function as<unverifiable tools>(a catalog mismatch), so no caller changes were needed. -
[SDK] Delegation hop verification and
verify_hitl_approval()called the Ed25519 primitive directly instead of going throughEd25519Verifier.verify_bytes(), so neither enforced the fixed 64-byte signature length before the bytes reached the crypto backend (SIGN-001, #122)._cose.pyand_revocation.pyalready checked the length themselves, so this gap was specific to_delegation.py. The backend already rejects a wrong-length signature safely on its own (checked oncryptography42.0.0, 46.0.6, 47.0.0, and 50.0.1), so this wasn't an active bypass, but both call sites now go throughverify_bytes()like the rest of the SDK.verify_hitl_approval()also now builds itsEd25519Verifierbefore the try/except that wraps base64 errors, so a badapprover_public_keyreports as a key error instead of being mislabeled as a bad signature encoding. -
[SDK] Bind presented memory delta operations to the consistency proof (#446).
verify_deltapreviously ignoredops, accepting substituted or missing operations under a valid root advance. Callers now pass only appended operations and the requiredrepresentationkeyword (kv,vector, orgraph). Malformed evidence returnsdrift; nonfinite budgets and unrepresentable TTLs fail closed. Roots and proof formats are unchanged. -
[DOCS] Correct section 9.1 applicability wording to the adopted Article 113(c) dates, with Article 111 transitions and the existing duty to verify deadlines retained (#410, Problem 2). Link the official consolidated Regulation; no field, conformance or requirement-level changes. Reported by @ioanavalea; maintainer-carried follow-up to #439.
-
[SDK]
MerkleTree.verify_inclusion()didn't checkInclusionProof.tree_sizeorleaf_indexagainst the tree it was called on, and its root-reconstruction arithmetic was an incomplete port of RFC 9162 §2.1.3.2. Two separate bugs, both in_merkle.py: -
A proof could verify against a tree other than the one it was generated for: a leaf-0 proof from a 4-leaf tree, replayed with a forged
tree_size=8, verified against the real 4-leaf tree's root, because nothing checkedtree_sizeagainst the tree's actual leaf count.verify_inclusion()now rejects a mismatchedtree_size, an out-of-rangeleaf_index, and anaudit_pathof the wrong length for that(leaf_index, tree_size)pair (new_expected_audit_path_length()). - Separately,
_compute_root_from_proof()was missing a reduction step RFC 9162 §2.1.3.2 requires, so genuine proofs failed to verify for non-power-of-2 tree sizes (e.g.tree_size=7, leaf_index=6). Checked every leaf of every tree size 1-300 (45,150 proofs): the old code failed 1,171 of them (2.59%), the fixed version fails 0. It also now rejects an out-of-range index or an over-lengthaudit_pathitself, so it's correct even called directly, not only throughverify_inclusion().
verify_inclusion() has no production callers outside _merkle.py. MerkleTree itself is used internally by the tree builders in this file (build_corpus_tree(), build_catalog_tree()), but isn't re-exported from the package root (only models.InclusionProof is); direct use of _merkle is not part of the package-root API.
-
[SDK]
verify_revocation_signature()comparedsigner_key_idwith!=instead of a constant-time compare, unlike every other key_id/digest check in the SDK. It now useshmac.compare_digest(), with an explicit guard forNoneand non-ASCII values (signer_key_idhas no ASCII/hex validator, andcompare_digestraisesTypeErroron non-ASCIIstrinput instead of returningFalse). -
[SDK]
manifest create/sign/attestcrashed with a raw AttributeError/TypeError if the input file was valid JSON but not an object (e.g.[]), instead of a clean CLI error._load_json()now checks for this, along with malformed JSON, non-UTF-8 files, and directories, and reports each cleanly (CLI-LOAD-001). -
[SDK]
attach_approvals()replaced the approvals array instead of adding to it, so a second approver's attach silently dropped the first approver's approval, whileattach_receipt()kept both. It now appends. It also raisesCoseStructureErrorifapprovalsis not a list, or if the envelope's existing approvals value is not an array (nullincluded), instead of overwriting it. To replace the array, useattach_unprotected(envelope, LABEL_APPROVALS, approvals). The COSE fuzz target now coversattach_approvals(). -
[SDK] ML-DSA-65 public keys weren't checked for length before use.
_split_hybrid_public_key()only checked that a hybrid key was longer than the 32-byte Ed25519 prefix, not that the rest was the correct 1952-byte ML-DSA-65 encoding, so a wrong-length key passed through and only failed later, opaquely, inside the crypto backend.MlDsa65VerifierandHybridVerifierhad the same gap - unlikeEd25519Verifier, they accepted a key of any length. The COSE (v0.2) path had it too:_ml_dsa_verify()called the backend directly instead of going throughMlDsa65Verifier, the way_ed25519_verify()already goes throughEd25519Verifier. All four now reject a wrong-length ML-DSA-65 key with a clearValueErrorat construction time. Trusted keys come from the relying party's own config, not the manifest, so no previously-VALIDmanifest is affected - a misconfigured key now fails clearly instead of reaching the backend unchecked. -
[SPEC] Align v0.2 transparency log semantics with the governing COSE envelope specification (issue #414).
The v0.2 manifest specification now removes the top-level signature and transparency_log_entry fields from the manifest shape. Transparency receipts follow the COSE envelope model defined by ADR-0011 and are carried through the receipts header. The v0.1 signing and transparency semantics remain unchanged.
-
[SDK]
verify_tdx_quote()now checks the PCK chain with the sharedverify_cert_chain(). It used to check only signatures, the root pin and validity, so a non-CA intermediate, a violatedpathLenConstraint, aKeyUsagewithoutkeyCertSign, or broken issuer/subject names still verified. Real Intel chains pass the stricter checks. A customtrusted_root_pemmust now be a CA certificate (BasicConstraintsCA=TRUE). -
[SDK] Malformed certificate material is now a rejection instead of a raw exception. A corrupt PCK chain PEM, an off-curve attestation key or a bad
trusted_root_pemraisedValueErrorfromverify_tdx_quote(), andverify_cert_chain()let malformed extensions or an unsupported issuer key escape (also through the TPM path). These now raiseTdxVerificationErrororCertChainError. -
[SDK]
manifest keygen(CRYPTO-008/SEC-005) wrote the private key file with the default umask, then chmod'd it to 0600 - briefly world-readable, and overwriting an existingprivate.hexcould leak a new key to anyone already holding the old file open. It now createsprivate.hexfresh withO_EXCLat mode 0600 and refuses to overwrite an existing one. -
[SDK] HITL approval
approval_duration_secondshandling didn't match spec 3.5, which requires a positive integer. Missing, zero, or negative values could be accepted as valid forever instead of being rejected; fractional values (e.g.1.5) were accepted at all, though the spec requires a whole number. ADR-0006: this field bounds how long an approval is valid, so an approval without a proper one isn't "unbounded", it's malformed. Fixed consistently across all three layers that touch it - schema validation, the exportedverify_hitl_approval(), and the integrated verifier's HITL loop (defense in depth if schema validation is ever bypassed) - via one shared check so the three can't drift apart again. -
[DOCS] Corrected the Art. 14(4) point citations and the satisfaction language in spec sections 3.5 and 9.1. Automation bias is point (b) rather than point (c), the interrupt and override capabilities are points (e) and (d) rather than point (a), and Art. 14(4) imposes no documentation obligation, since it is addressed to how the high-risk AI system is provided to the deployer. The approvals and the runtime fields are evidence a deployer can use when demonstrating oversight rather than satisfaction of Art. 14. Article 14 of Regulation (EU) 2024/1689 was read against the Official Journal text before being cited. (#410)
-
[SDK]
check_validity_period()(_cert_chain.py) rejected a certificate checked at the exact second of itsnotAftertimestamp. RFC 5280 §4.1.2.5 defines the validity period as "the period of time from notBefore through notAfter, inclusive," andcryptography'snot_valid_before_utc/not_valid_after_utcdocument the same inclusive semantics; the check used a strict<on the upper bound instead of<=. This is the single shared primitive behind every certificate-chain verifier in the package (SEV-SNP VCEK/ASK/ARK, TDX PCK chain, TPM AK chain, andverify_cert_chain()itself), so the off-by-one affected all of them identically. The bound is now<=, matching both the RFC and the library's documented behavior; the lower bound was already inclusive and is unchanged. -
[SDK]
verify_manifest()now recomputes a tool catalog's Merkle root from its suppliedtoolsbefore accepting the declaredcatalog_hash. A valid signature and a matching runtime hash no longer hide inconsistent catalog contents (spec 3.2.3, issue #416). The runtime comparison remains independent, and legacy bindings that omittoolsretain their existing hash-comparison behavior. Observed by solloek369-arch on #340 and filed as #416 by Imran Siddique. -
[SDK]
manifest sign(CLI) overwrote the signature block'ssigned_atafterEd25519Signer.sign()had already set it correctly, replacing the canonical_signed_at_now()value (UTC, second precision,Z-suffixed - the formatMlDsa65SignerandHybridSigneralso produce, regression-guarded for #165) withdatetime.now(timezone.utc).isoformat(), which emits a+00:00offset and a microsecond component instead. The redundant line has been removed; the CLI now writes exactly what the signer produced. Signatures already issued by the CLI remain valid -signed_atis not part of the signed pre-image - but downstream tooling that parses the field against the library's documented format would have rejected CLI-signed manifests that other signing paths accept. -
[SDK]
verify_manifest()returnedMISMATCH/EXPIRED/INVALID/UNVERIFIABLEforhitl_recordwhen any approval inhitl_record.approvalsfailed, even if a later approval in the same array was present, valid, unexpired, and sufficient for the declared risk tier. Spec 5.3 only requires at least one approval to satisfy every condition; it does not require every approval in the array to. The verifier's loop broke out on the first expired, malformed, unverifiable, or insufficient approval it encountered and never evaluated the approvals after it, so an operator who appended a fresh re-approval ahead of (or alongside) an old, expired, or otherwise stale one had the whole record incorrectly rejected. The loop now evaluates every approval and only short-circuits once one is found that clears every check. When none do, the prior code never had a real precedence to preserve: it broke the whole loop on the first approval that failed, so the reported reason was whichever check that one approval happened to fail first, not a ranking of failure types (an[EXPIRED, INVALID]array reportedEXPIRED;[INVALID, EXPIRED]reportedINVALID- purely positional, order-dependent, and never a documented contract). Reviewing this against multiple approvals for the first time requires an actual decision here, since more than one failure reason can now be true at once. This fix establishes and tests an explicit, order-independent precedence:INVALID>APPROVAL_INSUFFICIENTEXPIRED>UNVERIFIABLE.INVALID,APPROVAL_INSUFFICIENT, andEXPIREDare each positive, concrete evidence of a problem and always add a mismatch, so their relative order never changes the overall result;INVALIDis ranked highest among them because a broken or tampered signature is the strongest evidence of active tampering.UNVERIFIABLE(this verifier has no trusted key to check that approval against) adds no mismatch, so it is ranked last and only reported when it is the only problem in the array - otherwise it would silently drop a concrete finding frommismatch_detailsand downgrade the overall result fromMISMATCHtoUNVERIFIABLE(HITL-004). -
[SECURITY][SDK]
attach_receipt(),attach_attestation(), andattach_approvals()could crash withcbor2.CBOREncodeErrorinstead of the documentedCoseErroron a malformed COSE envelope._decode_tagged()checked that the unprotected header was a CBOR map but never inspected its contents; a stray break byte (0xff) in the wrong position decodes into cbor2's internal break-marker sentinel rather than raising, and that sentinel travelled throughattach_unprotected()undetected until the re-encode at the end, where cbor2 cannot serialize it. Same underlying cbor2 quirk as the signature-slot check_decode_tagged()already applies tobody[3]; this closes the same hole forbody[1]'s contents._decode_tagged()now recursively rejects the sentinel anywhere in the unprotected header - inside aMapping/list/tuple, inside aCBORTag(an unrecognised semantic tag), and inside aset/frozenset(cbor2 auto-decodes tag 258 to aset) - before any caller touches it, andattach_unprotected()converts any residualCBOREncodeErrortoCoseStructureErroras a second layer. -
[SECURITY][SDK]
POST /verify(JSON) had no body-size or field-cardinality cap, unlikePOST /verify/cose(issue #383).verify_cosebounds the CBOR body againstContent-Lengthand then against the actual byte stream before parsing it ("Content-Lengthis a claim, not a guarantee"), butverify_posttook aVerifyRequestPydantic model directly, so FastAPI fully buffered and validated an arbitrarily large JSON body - e.g. atrusted_keysdict with millions of entries - beforeverify_manifestever ran.verify_postnow takes the raw request, stream-caps the body atMAX_VERIFY_BODY_BYTES(the renamed, now endpoint-neutralMAX_COSE_ENVELOPE_BYTES, 1 MiB, shared by both/verifyroutes) before handing the bytes toVerifyRequest.model_validate_json(), and returns413 VERIFY_REQUEST_TOO_LARGEover the limit, mirroringENVELOPE_TOO_LARGE.VerifyRequest's seven dict/set fields (trusted_keys,trusted_key_issuers,delegation_public_keys,approver_public_keys,verified_transparency_entry_ids,verified_transparency_receipt_hashes,verified_attestation_manifest_hashes- the last of which the original report's field list omitted, but which has the identical unboundedset[str]shape and reachesVerificationContextthe same way) now also reject more thanMAX_VERIFY_COLLECTION_ENTRIES(10,000) entries, which catches a small body hiding an oversized collection behind short keys/values that a byte cap alone could miss.GET /verifyis unaffected; it takes no body. Taking a rawRequestinstead of aVerifyRequestparameter also meant FastAPI's own automaticContent-Typegate no longer ran for this route, soverify_postwould parse a JSON body submitted under any (or no)Content-Type-text/plain, a bogus subtype, form-encoded - contradicting theapplication/json-only contract the endpoint's own OpenAPI schema advertises.verify_postnow checksContent-Typeexplicitly, using the same rule FastAPI itself uses for a Pydantic-model body parameter (application/json, a+jsonvendor subtype, or no header at all; anything else is415 UNSUPPORTED_MEDIA_TYPE), before the body is read. Separately, bothverify_postandverify_cosechecked the accumulated body againstMAX_VERIFY_BODY_BYTESafter appending each stream chunk rather than before, so the buffer could be grown past the cap by up to one chunk's size before being rejected; both now check before appending, so the buffer itself is never intentionally grown past the limit. -
[SECURITY][SDK]
_strict_schema_violations()tolerated a missing top-levelissuerclaim for any manifest version, not just v0.1. The exception was added for legacy v0.1 records, which predate theissuerfield (CHANGELOG: "legacy v0.1 issuer omission remains compatible"), but the filter never checked which version was being verified. The v0.2 spec makesissuerREQUIRED, and it is not decorative:_signature_key_issuer_mismatch()uses it to authorize the signing key. As a result, a v0.2 manifest withissuerstripped out but otherwise intact and carrying a valid COSE signature could pass schema validation and reachVALID, silently dropping that authorization boundary for any manifest an attacker (already in possession of a valid signing key) chose to omit it from. The exception is now scoped toversion == "0.1"; a v0.2 manifest missingissuerfails closed with aMISMATCHschema violation, same as any other missing required v0.2 claim. -
[SDK]
verify_delegation_chain()no longer verifies a delegation chain asVALIDwhen a hop'sscope_grant.constraintsis non-empty. Constraints are Cedar statements (spec 3.4.1); this package has no Cedar parser or evaluator, so per spec such a chain MUST be reportedUNVERIFIABLE, neverVALID._check_scope_narrowing()only ever did a string-set comparison between parent and child constraints, so two hops carrying the identical, unparseable constraint string (nothing "dropped") verified asVALIDend-to-end. A newDelegationUnverifiableexception now signals this case distinctly from an affirmatively broken chain (ValueError→INVALID);verify_manifest()maps it toDelegationResult.UNVERIFIABLE. -
[SECURITY][CLI]
manifest verifygained--crl-trusted-key PATHto authenticate--crl-pathrevocation records against the revoking authority's public key. Previously the CLI always constructedFileCRLwithouttrusted_signer_key, so every CLI-driven CRL load ran inFileCRL's unauthenticated mode regardless of intent: a party who could write to or intercept the CRL file could fabricate a revocation record for a legitimate manifest, andmanifest verifywould accept it without complaint.--crl-trusted-keyis now required to authenticate the CRL; omitting it still works (unchanged default) but now prints a loud warning, and passing it without--crl-pathfails cleanly instead of being silently ignored. Same pattern as the--approver-keygap fixed in 0.11.1. -
[SECURITY][CRL]
FileCRL._load()now fails closed in authenticated mode: a malformed, unsigned, tampered, or wrong-key record used to be silently skipped, which meant a party able to corrupt one line of an authenticated CRL file could make that manifest register as not revoked, the exact outcome--crl-trusted-keyexists to prevent. Any such record now raisesCRLIntegrityError(surfaced by the CLI as a clean, non-zero-exit error) instead of silently disappearing from the in-memory set beforeis_revoked()runs. -
[DOCS] Corrected an overclaim in the
--crl-trusted-keyhelp text andmanifest verifydocs: authenticating records that are present in a CRL file is not the same as proving the file is complete. Per-record signatures cannot detect that a valid line (or the entire file) was deleted, so--crl-trusted-keydoes not by itself prevent un-revocation by deletion. Closing that gap needs a signed, versioned CRL snapshot/digest mechanism, which is not yet implemented. -
[SECURITY][SDK]
verify_attestation_chainno longer treats theazure-cvm-sev-snpplatform label -- or a caller-supplied boolean -- as proof that the manifest binding was verified. REPORT_DATA on Azure issha256(runtime_data), never the manifest hash, so it cannot be checked directly the way it is on bare-metal SEV-SNP; Azure's real binding is a vTPM AK-signed quote over a PCR derived from the manifest hash, chained to REPORT_DATA via the runtime data. An earlier revision of this fix took anazure_manifest_binding_verifiedparameter and trusted whatever the caller passed in -- which meant any caller (or future code) could getpassed=Trueby simply passingTrue, with no evidence ever checked. That parameter is gone.verify_attestation_chainnow establishes the composite chain itself (PCR-in-quote, AK signature, AK identity in runtime_data, runtime_data->REPORT_DATA binding -- see the newagent_manifest._azure_verify.verify_azure_manifest_binding, also used byAzureCVMProvider.verify_manifest_in_reportso there is exactly one implementation of this security property) directly from evidence carried on the report (quote_msg,quote_sig,ak_pub_pem,runtime_data_hexinreport.raw, and the SNP report bytes).report_data_matchedis a definiteTrue/Falsefor Azure, the same as every other platform. Aplatformvalue selects which verification procedure applies; it is never itself evidence that the procedure ran, and neither is any caller-supplied flag -- there is no longer one to supply. -
[SECURITY][SDK]
verify_attestation_chainplatform dispatch is now an explicit allow-list (amd-sev-snp,azure-cvm-sev-snp,intel-tdx,tpm,aws-nitro) instead of a catch-allelsethat routed any unrecognized platform label through the SNP verifier. Unsupported labels now reportNOT_IMPLEMENTEDand cannot pass. Closes #363. -
[SDK]
_check_manifest_binding()no longer masks a malformedartifactsorartifacts.policy_bundleas merely absent. Truthy non-objects ("str",[1],True) already correctly reportedmanifest_artifacts_not_an_object/manifest_policy_bundle_not_an_object, butmanifest.get(...) or {}folded any falsy non-object ("",[],False,0) into{}before theisinstancecheck ever ran, so those were reported asmanifest_has_no_policy_bundle_hash"no hash available" instead of the structural warning. A value is now only treated as missing when the key is absent or explicitlyNone; any other non-dict value reports the structural warning regardless of truthiness.
[0.12.0] — 2026-09-05¶
Security¶
Five privately reported advisories, all the same shape: the integrated verifier reaching a passing verdict on evidence that does not support it.
- [SDK]
attestation_verifiednow requires an independent hardware appraisal (GHSA-85fc-3g4g-fjjc, GHSA-qvg2-j8c5-5x3w). It was set by comparing the manifest's ownmanifest_hash_in_reportagainst the locally computed manifest hash. That binding proves the report names this manifest; it proves nothing about hardware. For v0.1 the attestation block is outside the signing pre-image (spec 3.3) and for v0.2 COSE it rides in the unprotected header, so anyone holding a validly signed manifest could append a digest they computed themselves and satisfyenforce_attestation=Truewith no TPM quote, SNP report, TDX quote, certificate chain or trusted root involved.
VerificationContext gains verified_attestation_manifest_hashes and attestation_evidence_manifest_id, on the same footing as the transparency receipt inputs. Breaking: a caller setting enforce_attestation=True without supplying an appraisal now gets ATTESTATION_UNAVAILABLE where it previously got VALID.
-
[SDK]
audit_key_sealedis enforced underenforce_attestation(GHSA-mqqg-9mpc-mpg7). Spec 5.3 requires it to be true. The engine readaudit_chain_rootbut never this flag, so true, false and absent all returned the sameVALID. -
[SDK] Expired manifests no longer verify when timestamp parsing fails (GHSA-7v9r-xprj-65j7).
issued_at/expires_atare typeddatetime, so Pydantic accepts epoch seconds and a lowercase zone designator and the schema gate passes them. The validity block re-parsed the raw strings withdatetime.fromisoformat(), which rejects both, and swallowed theValueError. The whole validity window was skipped and a correctly signed but expired manifest returnedVALID. Timestamps now parse through the same adapter the model uses, and anything still unreadable fails closed. The memory-baseline TTL had the identical fail-open handler and is fixed too. -
[SDK] HITL
approval_methodis covered by the approval signature (GHSA-q8mp-875w-2w53, GHSA-wfv4-3xwh-9f2h). It decides Level-2 sufficiency but sat outside the pre-image, so asoftware-keyapproval could be relabelledhardware-keywith its signature untouched. Breaking: approvals that carryapproval_methodmust be re-signed. Approvals without one are unaffected, because the key is omitted from the canonical object when the field is absent. -
[SDK] TPM AK chains are appraised by the shared verifier (GHSA-mp83-94pc-7wqh).
_verify_ak_chain()checked only "signed by the next" plus a pinned root, accepting non-CA intermediates and intermediates whoseKeyUsageforbids certificate signing. It now delegates toverify_cert_chain(), the same appraisal the SNP and TDX paths use. Malformed PEM also escaped as a rawValueErrorpast a dispatcher catching onlyTpmVerificationError. -
[SDK] A nested omission can no longer mask the full-binding requirement (GHSA-6hjj-gh3c-r6wv).
_validate_manifest_profileis amode="after"model validator, so deleting a required field nested inside an artifact binding stopped it running, and the nestedmissingerror was then filtered for legacy compatibility. Removing more from a manifest made the verdict better. The requirement is now checked directly on the document. In practice it was unenforced for any manifest with minimal artifact bindings, not only the two-deletion case.
Conformance: AM-VEC-018 asserted the old attestation behaviour and is corrected; AM-VEC-018d and AM-VEC-018e cover the appraised and replay cases. These are language-neutral vectors, so this is a contract change.
Changed¶
- [CI] All 35 third-party action references pin a commit SHA rather than a mutable tag.
Fixed¶
- [SDK]
verify_cert_chain()now enforcesBasicConstraints.path_length(RFC 5280 §4.2.1.9). An issuer that declarespath_length=Nbut has more thanNCA certificates between it and the leaf previously verified successfully every other individual check on it (validity period,caflag, key usage) passed on its own, so nothing else caught it. Self-issued certificates (CA key rollover) are not exempted from the count; this is the fail-closed direction relative to the full RFC algorithm.
Deprecated¶
- [SPEC] Issuing v0.1 manifests ends 2026-11-30 (issue #315, phase 5). From that date the reference implementation produces v0.2 COSE envelopes only. The date follows phase 4 completing:
cmcpandca2aboth verify a v0.2 envelope through their real loader paths and both reject a payload declaring v0.1 under a v0.2 envelope (AM-VEC-COSE-012), so a consumer is exercising v0.2 rather than a test harness.
Verifying v0.1 manifests is not deprecated and has no end date. These are audit records with regulated retention well beyond their 90-day validity, and a verifier that stops reading them destroys evidence rather than tidying a codebase. Same reasoning as keeping the -8 Ed25519 code point acceptable indefinitely.
Fixed¶
- [SDK]
verify_tpm_quote()now distinguishes a legacy bare AK signature from a marshalledTPMT_SIGNATUREusing the attestation key's signature shape, rather than an allowlist of the envelope's untrustedsigAlgprefix. Changing a reference envelope to declare an unsupported scheme now raises the explicit unsupported-algorithm error instead of silently falling through to the legacy verifier and returningFalse; the mutated envelope remains rejected, with only its diagnostic changing. Modulus-sized bare RSA signatures whose random prefix resembles a supported scheme now remain in the documented legacy lane and can verify normally.
[0.11.2] — 2026-08-27¶
Fixed¶
- [SECURITY][SDK]
verify_manifestnow binds a declaredartifacts.policy_bundle.enforcement_modeto the runtime's attested mode. A mismatch or an omitted runtime mode fails closed instead of accepting a matching policy hash while the runtime operates with weaker enforcement.
[0.11.1] — 2026-08-23¶
Fixed¶
-
[SECURITY][SDK] The integrated verification path now authenticates every enforced HITL approval against a trusted approver key and binds that approval to the manifest being verified. Missing keys, malformed or invalid approval signatures, and approvals replayed from another manifest fail closed. See GHSA-ww2p-prj4-c6xf.
-
[SECURITY][CLI]
manifest verifygained--approver-key APPROVER_ID=PATH(repeatable) to supply trusted HITL approver keys. Without it the CLI had no input that could populateapprover_public_keys, so every approval resolved toUNVERIFIABLEand--enforce-hitlcould not succeed. A failing result now names the missing option. -
[SECURITY][SDK] Bound runtime artifacts now fail closed when their observed hashes are absent. Signature-only appraisal remains available only through an explicit API or CLI opt-out and must not be used as authorization evidence about a deployed agent.
-
[SECURITY][SDK] Transparency receipts are no longer treated as a warning- only attachment at production conformance levels. Level 1+ (or explicit
require_transparency) requires an entry ID or receipt digest produced by an independent appraisal against the relying party's trusted transparency-log policy. An attacker-controlled receipt in a COSE unprotected header isUNVERIFIABLE, not proof of inclusion.
Added¶
- [SPEC][SDK] Added configuration assurance for artifact #1 (spec 3.2.1.1, issue #254). Every threat in section 7.1 is an adversary substituting something, and
system_prompt.hashdetects that. A prompt authored in good faith, reviewed, approved, signed, sealed to a TEE measurement and verifiedVALIDmay still specify behaviour the deploying organisation would reject if it were tested rather than read, with no adversary present and no digest changed. Artifact #1 is the specification of the agent's behaviour rather than data it consumes, so digest equality is necessary and not sufficient.
system_prompt.assurance_test binds a named suite, suite version, harness version and result, the same shape and the same normative force section 3.2.5 already gives poisoning_scan: result: flagged MUST NOT be issued as VALID. The verification result gains configuration_assurance, so absence is reported as NOT_ASSESSED rather than inferred as a pass. Which conformance level requires an assessment is deliberately left open.
safety_level is now named in the spec as an operator assertion with no defined value space and no verification behaviour, which is what it always was; the field name resembled an assurance signal and nothing said otherwise.
-
[SPEC] Stated the scope boundary of the memory checkpoint protocol (section 3.2.6.2, issue #298). A governed advance establishes integrity, ordering, freshness and budget, and establishes nothing about retrieval behaviour over the new state. A checkpoint can satisfy every rule while an appended correction stays less salient than the fact it corrects, while authorized additions displace a safety or identity anchor, while another tenant's memory becomes retrievable, or while states that should retrieve differently collapse to the same context. None of that needs a forged signature or a broken proof, so the protocol's own checks cannot see it. Assessment is a separate evidence artifact over a pinned retriever and probe suite, out of scope here and tracked in #298.
-
[SPEC][SDK] Withdrew the artifact-only refresh path at Level 1 and above, and made a stale attestation fatal (issue #265). Section 2.2 let
memory_baseline.snapshot_hashbe renewed and the manifest re-signed without re-running the TEE attestation. That cannot hold:manifest_hash_in_reportis computed over the full manifest including thesignatureblock, so the refresh changes the attested pre-image twice and the retained report binds the previous document. A verifier had to either reject the refreshed manifest or stop enforcing the binding, and both cannot be conformant at once. Governed memory evolution belongs to the section 3.2.6.2 checkpoint protocol, which exists so a growing store does not mutate the attested document.
The verifier previously reported a mismatched manifest_hash_in_report only when enforce_attestation was set, so the default path returned VALID for exactly the case above. A present attestation binding a different manifest is now MISMATCH regardless: enforce_attestation governs whether an attestation is required, not whether a wrong one counts. AM-VEC-021 pins it, and examples/level1-tpm-attested.json carried a placeholder hash that bound no document, which the new rule surfaced.
-
[SPEC][SDK] Added the audit-chain continuity protocol (spec Section 3.2.7.1, issue #273).
audit_chain_rootis the chain state at signing time and decisions are produced after signing, so a verifier fetching the chain later could confirm it still contained the signed root but not that it was an append-only extension of it rather than a chain rebuilt around that prefix.entry_countdetects deletion below the signed count and constrains nothing above it. The chain now gets the mechanism Section 3.2.6.2 already gives memory:merkle-logadvances carry an RFC 9162 §2.1.2 consistency proof,hash-chainedadvances carry the ordered entry leaves folded forward from the signed root, and both walk the same fail-closed stages (proof, monotonicseq, TTL).decision_tracegains anEXTENDEDresult for a proven extension; a diverged root with absent, malformed, or failing evidence is stillMISMATCH. There is no budget stage, because an audit chain is meant to grow without bound. A verified proof establishes that nothing below the head was rewritten; it does not establish that every action produced an entry, and the section says so. -
[SPEC] Added section 5.3.2, what
VALIDdoes not establish (issue #272). Section 5.3 defined behaviour onMISMATCHand none onVALID, so a gateway reading it literally had a rule for one outcome and a natural default for the other.VALIDis a statement about provenance, not permission: it does not establish that the call about to be made is permitted, that it is within the consequence envelope an approver had in mind, or that the agent's current inputs are trustworthy.catalog_hashpins which tools were approved, and inside one authorized tool a read and an irreversible write are the same tool. Per-call authorization joins the section 7.2 out-of-scope list, and the overview sentence claiming the manifest proves "what it was allowed to do" is narrowed to what it actually binds. No schema, field, or conformance change; the conformance test from #280 already demonstrates the boundary. -
[SPEC][SDK] Resolved the
agent_idlifetime overload and made the OCSF identity mapping normative (spec 3.1 / 6.4.2, issue #269).agent_idwas serving both of OCSF's identity roles at once: the stableai_agent.uidand the session-scopedai_agent.instance_uid, which is why the crosswalk shipped informative.agent_idis now defined as the stable identity, and instance scope is declared in a new OPTIONAL signedagent_instance_idrather than parsed out of a SPIFFE path segment that carries no declared meaning. A producer emittingai_agentfor a manifest-governed session MUST populateuidfromagent_id, MUST populateinstance_uidfromagent_instance_idwhen the manifest declares one, and MUST NOT fall back toagent_idforinstance_uidwhen it does not. The verification result gains acorrelationobject carrying both identities and themanifest_idthey came from. Adding the field leaves every existing signature verifying, since the pre-image omits absent fields; the conformance vectors are regenerated for the longersigned_fieldslist. -
[SPEC][SDK] Added the relying-party challenge and context binding for verification results (spec 5.1.2, issue #266).
verification_idis chosen by the verification service andverified_atis a producer-selected timestamp, so neither shows a relying party that a result was produced for its live request: a signedVALIDcould be replayed to a different party, for a differentpurpose, or against a weaker context than the one now being asked for. A request may now carrychallenge_nonce(at least 128 bits, single use), which the result echoes unchanged, alongsideverification_context_hashover the RFC 8785 canonical form of the request context. The nonce is deliberately outside the hash so two requests asking the same question hash the same. A result with no nonce is unbound to any request and 5.3 now says it must not gate an action.
derive_runtime_nonce() composes this challenge with the section 3.3.2 runtime attestation report: sha256("am-runtime-nonce" || nonce), domain separated so the derived value cannot be presented as a verification challenge itself. Without it the two freshness domains are replayable independently, which defeats both.
-
[SPEC][SDK] Added the
com.agentrust-io.manifestAgent Plugins 1.0.0 extension profile. It resolves an HTTPS manifest by raw-byte digest, verifies it against independently trusted keys, and compares the local bundle with a signedsource_bundlebinding. Absent, unreachable, unverifiable, and mismatched references remain distinct outcomes. -
[SPEC][SDK] Added the signed
composition-onlyprofile for repository and pre-execution manifests. Every omitted artifact must be named inunbound_artifacts; overlap and undeclared omissions fail closed. These documents verify asINCOMPLETEand cannot claim Level 0 or above. -
[SDK]
parse_tpm_attest()now exposes the common signedTPMS_ATTESTheader and opaque union payload, whileparse_tpm_nv_certify()enforces the signed type and parses theTPMS_NV_CERTIFY_INFOcarried byTPM2_NV_Certify. This lets cMCP retire its remaining local NV-certify wire parser. Size-prefixed attestations now reject undeclared trailing bytes.
[SDK] AM-VEC-COSE-002 … AM-VEC-COSE-015 publish the COSE negative conformance vectors. Fourteen cases a conforming verifier must not accept, covering the protected/unprotected header split, CBOR tagging and framing, payload presence, the issuer authorization boundary, the two JSON parser divergences RFC 8259 leaves open (duplicate member names, non-finite numbers), version routing in both directions, and the payload depth bound. 011 and 015 are a pair: #243 records NaN and Infinity as one class of defect, but they are not one code path in every parser, so a verifier that special-cases NaN passes the first and fails the second. They complete the portable contract other-language SDKs are written against: until now the suite proved an implementation could accept a valid envelope, not that it rejected an invalid one.
AM-VEC-COSE-014 is the reverse of 012 and the reason the version gate is described as bidirectional: a v0.2 manifest must not fall back to the v0.1 detached signature block, because a one-way gate is not a gate. It expects MISMATCH rather than INCOMPATIBLE_VERSION, since the verifier does support 0.2 and reporting otherwise would state something untrue about its capabilities.
The hybrid authorization case, a COSE_Sign carrying one authorized component key alongside one unauthorized one, is deliberately not a vector: it contains an ML-DSA-65 signature, ML-DSA-65 signing is hedged, and cryptography 49 exposes no deterministic mode, so the bytes differ on every regeneration. It remains covered by a per-run test in the Python suite, and the vector README states the rule as binding on other languages so it is not mistaken for out of scope.
Each negative carries signature_valid, recording whether the Ed25519 signature over the RFC 9052 Sig_structure verifies. Where it is true, a verifier cannot pass the vector by rejecting a broken signature and never reaching the rule the vector names. The vectors whose defect is in the payload are signed over the malformed bytes rather than having bytes swapped into an already-signed envelope, which is what keeps that guarantee. Two declare false by design: AM-VEC-COSE-002 tampers with the protected header, which is the rule under test, and AM-VEC-COSE-008 has a nil payload, so there is no Sig_structure to verify over.
AM-VEC-COSE-009 is byte-identical to AM-VEC-COSE-001 and differs only in context.trusted_key_issuers. Nothing about the object explains the rejection, so a verifier that stops at "the signature verifies under a trusted key" returns VALID and has no authorization boundary at all.
Fixed¶
-
The committed conformance vectors are now diffed against a fresh in-memory regeneration by the test suite, so a vector edited by hand, or a generator change made without regenerating, fails CI rather than shipping as a contract nobody can reproduce.
-
The vectors had been stale each time
SIGNED_FIELDSgained a member without being regenerated alongside it:intentin 0.11.0,profileandunbound_artifactsin #306,source_bundlein #307. In each case they they published asignature.signed_fieldslist omitting those fields, andAM-VEC-018carried themanifest_hash_in_reportthat followed from the shorter list. No signature or expected result changes, because none of these manifests declares any of those fields andsigning_pre_imageomits absent ones, so the signed bytes are identical either way. What was wrong is what the suite told other languages to build their pre-image from. The regeneration test above is what surfaced it, and is what stops it recurring.
Security¶
verify_manifest()now fails closed when a core identity, validity, or artifact-container claim is missing. A valid signature no longer turns such a structurally incomplete object into aVALIDmanifest; legacy v0.1 issuer omission remains compatible unless issuer authorization is configured.
All notable changes to Agent Manifest are documented here. Format follows Keep a Changelog. Spec changes are marked [SPEC]; SDK changes are marked [SDK].
Added¶
- Added an informative Agent Credentials integration guide describing how a relying party joins a credential decision to the exact signed manifest and runtime-evidence record at a defined execution boundary. The guide tracks CoSAI WS4 issue #99 and OCSF proposals #1704 and #1724 without treating either open proposal as released schema. Closes #267.
Security¶
- The PyPI release workflow now installs and smoke-tests both the exact wheel and source distribution before upload, including version/tag agreement, import provenance, a public cryptographic verification roundtrip, and the packaged CLI entry point.
Security¶
- Generic hardware-attestation certificate-chain verification now enforces every certificate's validity period and requires every issuing certificate to carry
BasicConstraints(ca=True). If an issuer declaresKeyUsage, it must permit certificate signing.
Fixed¶
- The Python test harness now pins imports to the checkout's
srctree and asserts that location, preventing a stale installedagent-manifestwheel from producing misleading release-validation results.
Security¶
-
FileCRL.revoke()now verifies a record's signature and signer key ID before mutating its cache or append-only file whenevertrusted_signer_keyis configured. This closes an append-time bypass of the existing load-time trust check. -
verify_manifest()now rejects manifests whoseissued_atis in the future and treatsexpires_atas an exclusive upper bound, enforcing the specification's fullissued_at <= now < expires_atvalidity window.
[0.11.0] — 2026-08-11¶
Added¶
[SPEC][SDK] Section 3.9 adds an OPTIONAL intent field, declared by the issuer. Runtime governance frameworks (AARM R2/R3 among them) ask that an action be evaluated against the agent's stated intent, and this specification had no such input. The field is one string, intent.statement.
The issuer declares it, and that is the whole design. An intent the governed agent asserts about itself cannot support the check it exists for: an agent that intends to do X declares X, and the comparison becomes a formality. intent is therefore inside the signing pre-image, fixed before the agent runs, and the spec says an intent supplied outside the signed manifest — on a per-call request field, say — MUST NOT be accepted as satisfying the section.
Adding a field to SIGNED_FIELDS would normally be a compatibility break and is not one here: signing_pre_image omits fields absent from the manifest rather than serializing them as null, so a manifest without intent produces byte-identical pre-image to before and its signature still verifies. A test asserts that against the exact bytes, and it fails if absent fields are ever materialized.
No digest is stored beside the statement. intent_hash() derives sha256:<hex> over the canonical intent object on demand, so a runtime can bind the intent into a per-call receipt without a second representation that can be made to disagree with the first. It returns None when no intent is declared, which is a distinct answer from a digest over an empty statement.
Section 3.9.1 states what the field does not give: it is an input, not an analysis. Nothing here measures whether an action is semantically consistent with the declared intent, that would need a model whose own execution would have to be attested, and an implementation MUST NOT present the presence of intent as evidence such a measurement happened. A structural proxy is not a semantic measure and MUST NOT be labelled as one.
[SPEC] Section 6.5 states the boundary with Agent Plugins (#281). Agent Plugins 1.0.0 shipped as a packaging format for Agent Skills and MCP server configuration, backed by a multi-vendor steering committee, and the obvious reading from outside is that this project is a second format for the same job. It is not, and the specification had nowhere that said so.
The boundary in one sentence: Agent Plugins describes what a client should install, a manifest describes what actually ran. A plugin bundle is an input to a manifest.
The section argues that from the format's own documents rather than by assertion. plugin.json requires two fields, $schema and name, version is an unconstrained string, and there is no integrity, signature or provenance field in the 1.0.0 schema. FUTURE_CONSIDERATIONS.md states that v1.0.0 "does not define a trust model, permission system, or sandboxing requirements for plugins" and "does not specify how clients or users can verify the origin or integrity of a plugin", and lists attestation chains linking a published plugin to its source repository and build as possible future work.
Of the ten artifacts, Agent Plugins carries two, and carries both as declarations rather than as resolutions: skills/*/SKILL.md holds instruction material, and mcp.json declares which servers to start without enumerating the tools those servers exposed. That declared-versus-resolved gap is the boundary in miniature.
It stays informative for the same reason 6.4 does. Binding to plugin.json now would fix a field layout in a 1.0.0 format whose own roadmap anticipates adding provenance, so it would have to be revised rather than refined once that lands. The extensions object, keyed by reverse-domain namespace, is noted as sufficient to carry a manifest reference without any change to the format.
[SDK] manifest from-plugin reads an Agent Plugins 1.0.0 bundle (#282), along with load_plugin_bundle, bundle_digest and system_prompt_binding_from_bundle. This is the runnable half of the boundary in spec section 6.5: a plugin bundle is an input to a manifest, and now there is code that treats it as one.
It does not build a tool manifest, and that is the point. ToolEntry requires a schema_hash and a description_hash for every tool. mcp.json declares which servers to start and never enumerates the tools those servers expose, so a bundle carries neither hash and cannot be made to. Building a ToolManifestBinding from a bundle alone would mean inventing the two values the artifact exists to bind. Declared servers are therefore recorded as declarations, each with a hash of its declaration so a later resolution can be compared against what the bundle said, and the CLI says on stderr how many still need resolving. A test asserts that no tool-manifest builder appears in the module, so this stays true by accident-proofing rather than by memory.
The digest covers every file in the bundle, including ones the adapter does not parse, such as the reverse-domain client extension directories the format permits. A digest that skipped what it did not understand would report the same value for two bundles differing in an unread file, which is the case where unmeasured and empty become indistinguishable. Paths are bound alongside content, so a rename with identical bytes is a different digest.
$schema is checked against the 1.0.0 constant and an unrecognised value is refused rather than guessed at. Both mcp.json shapes seen in the wild are accepted, the mcpServers wrapper and a bare mapping, because rejecting the second would fail bundles every client accepts.
[SPEC] Section 6.4 is an informative crosswalk to OCSF runtime evidence (#269). There was no defined join key between a manifest and the OCSF events emitted under it, so a consumer holding both could not tell they described the same agent without an out-of-band convention, and implementers were left to invent a second identity mechanism for a job this specification already does. The new section records the intended correspondence and deliberately requires nothing.
Three things it gets right that a normative version could not yet: it is written against the ai_operation profile, which is what actually contributes ai_agent, rather than against an event class (the Agent Inventory Info [5050] class proposed for this does not exist in OCSF as of this writing; a Discovery-category class for agent trust-base inventory is now proposed upstream at ocsf/ocsf-schema#1724); it maps ai_agent.uid to the durable identity and instance_uid to the session-scoped one, which is what OCSF's own definitions ask for, instead of collapsing both onto agent_id; and it notes that session_uid is not an ai_agent attribute at all.
It stays informative because agent_id is one field serving both roles, and section 3.1 says the /agent/<name>/<instance> path is "a convention, not a requirement" — so a conformant agent_id may be stable and carry no instance scope. Requiring it to populate instance_uid, which OCSF defines as explicitly distinct from the stable uid, would force a stable identifier into the non-stable field and cost a consumer the ability to separate "every run of this agent" from "this run". Resolving that means deciding whether agent_id splits, which belongs in CoSAI WS4 alongside the canonical @context URL that section 3.1 already defers there.
The section also records a correspondence worth more than the identity one: OCSF's delegation object (uid, parent_uid, forming a re-delegation DAG) is structurally the delegation chain of section 3.4. It does not map today, because a section 3.4 hop has no per-hop durable identifier to populate delegation.uid, and OCSF wants that identifier issued by a trusted authority rather than self-asserted by the delegator. Closing that is a data-model change, not a crosswalk, and is not proposed here.
[SPEC] Section 9.1.2 maps EU AI Act Article 50, and maps it as a gap. Article 50 transparency duties have applied since 2 August 2026: chatbot disclosure, machine-readable marking of synthetic output, emotion-recognition notice. It is the only AI Act obligation in force against an agent deployment today, and no document in this repository mentioned it. The manifest satisfies none of the four paragraphs, so the new subsection says that in those words, notes that Article 50 applies regardless of Annex III classification, and states that a manifest MUST NOT be read as Article 50 evidence. docs/compliance/eu-ai-act.md carries the same mapping for auditors. Marking that binds to the attested agent that produced the output, rather than to a strippable metadata field, is the design being pursued; it is not built.
[SDK] COSE_Sign1 signing and verification for manifest version 0.2 (_cose.py, ADR-0011, ADR-0013). Phase 2 of the envelope migration (issue #243). verify_manifest() now takes either a dict or bytes: a dict is a v0.1 manifest and verifies exactly as it does today, bytes are a v0.2 COSE envelope. The envelope follows the manifest version, never a flag, so no existing record is reinterpreted.
Fixed¶
[SPEC] The README pointed at a specification file that does not exist. Three links and the spec badge referenced spec/agent-manifest-spec-v0.1.md, superseded by v0.2. All four now resolve.
Changed¶
What the envelope buys is structural rather than incremental. alg is in the protected header and covered by the signature, so the downgrade fixed in 0.6.0 by an explicit cross-check cannot be expressed at all. The payload is verified as received, so RFC 8785 is no longer an input to verification — it stays the producer-side determinism rule and the basis of the hash bound into hardware. Receipts (label 394), the TEE attestation report, and HITL approvals attach in the unprotected header, which retires the signed_fields coverage table, the hitl_record.approvals normalization rule, and the transparency_log_entry ordering rule together. Hardware now binds sha256 of the payload bytes, with no field subset to keep in sync.
Hybrid is one COSE_Sign with two signers rather than two COSE_Sign1 objects, so both signatures covering identical payload bytes is a property of the structure. A verifier that cannot perform ML-DSA-65 returns UNVERIFIABLE and never falls back to the classical entry.
[SDK] New runtime dependency: cbor2. Serialization only. The COSE structures are built in this repository and every signature stays on cryptography (and optionally pyoqs), so the SDK's crypto surface is unchanged. ADR-0013 records why no COSE library was taken: neither pycose nor cwt ships the RFC 9964 code points that half this envelope needs, and both widen the dependency closure to do less.
Fixed¶
[SDK] The pq extra was uninstallable, and the module it imported is not the package it meant. pip install "agent-manifest[pq]" required pyoqs, which is not published on PyPI, so the post-quantum profile could not be installed by following the documented instruction. Worse, the module name the SDK imports — oqs — belongs to an unrelated project on PyPI, and _signing treated a successful import as proof of liboqs. Installing that package turned every ML-DSA-65 call into an AttributeError (18 test failures) and made the SDK report a post-quantum capability it did not have.
ML-DSA-65 now comes from cryptography, which implements it through OpenSSL as of 47.0.0 and is already a required dependency. The pq extra is now cryptography>=47 and installs cleanly. Deployments already carrying the liboqs bindings keep working: the backend is chosen by the key material, not by configuration, because the two differ in private key encoding — cryptography uses the 32-byte seed, liboqs the expanded secret key. Public keys are the same 1952-byte encoding in both, so key_id, the COSE kid, and every signature a third party verifies are unchanged and interoperable across backends. A build with neither backend still reports UNVERIFIABLE rather than accusing the manifest, unchanged from #245.
The capability check no longer trusts a module name: liboqs is now identified by its API. Thirteen post-quantum tests that skipped on every machine without liboqs — across signing, hybrid mode, evidence packs, and the COSE envelope — now execute against real FIPS 204 signatures.
Changed¶
[SPEC] EU AI Act mappings now carry the date each obligation applies from. The mappings were written as if the high-risk obligations bind today. Under the current provisional timeline the Digital Omnibus amendments defer Annex III systems to around 2 December 2027 and Annex I to around August 2028, which docs/compliance/eu-ai-act.md already recorded and nothing else did. The mappings are correct and unchanged; only the applicability date is added, in section 9.1 (authoritative note), the section 8.1 conformance-level table, the section 8.1 log-retention requirement, LIMITATIONS.md, the HITL tutorial and example, docs/index.md, docs/getting-started.md, and the hardware-attestation tutorial. Where a deployment has an obligation in force today, the docs now lead with it: DORA for financial entities, HIPAA § 164.308(a)(5) for the HITL record.
[SPEC][SDK] Ed25519 is identified by -19, not the deprecated EdDSA -8 (ADR-0014). RFC 9864 (Standards Track, October 2025) deprecated the polymorphic EdDSA identifier and registered fully-specified ones; the IANA COSE Algorithms registry marks -8 deprecated. Issue #243's "code points are settled: EdDSA -8" was therefore already out of date when phase 1 was written — the ML-DSA-65 half (-49) was confirmed correct against IANA. A producer now signs with -19; a verifier accepts both and keeps accepting -8 indefinitely, because manifests are audit records with regulated retention and a signature cannot be re-issued under a new identifier without re-signing.
-8 and -19 name one algorithm, so anything reasoning about which algorithm signed compares algorithms rather than code points: a post-quantum profile is satisfied by neither, and a COSE_Sign carrying one entry of each is rejected as a single algorithm signed twice rather than accepted as a hybrid signature.
The cost is recorded in the ADR rather than glossed: no COSE library implements RFC 9864 yet, so a -19 manifest cannot currently be verified by third-party tooling — the interop check runs against -8 fixtures over the same structures for as long as that holds.
[SDK] AM-VEC-COSE-001 pins the COSE_Sign1 encoding byte-for-byte. A vector now carries either manifest (v0.1) or envelope_hex (v0.2 COSE), and the same conformance loop runs both, since the engine selects the procedure from what it is handed. The vector fixes every element — tag, protected header, unprotected_hex of a0, payload, signature, and the payload hash hardware binds — so another language's SDK has something to agree with rather than only its own round-trip. Ed25519 only: ML-DSA-65 signing is hedged, so post-quantum envelopes differ per run and only their structure is stable.
[SDK] A version 0.2 manifest may not use the v0.1 envelope. The version gate now binds in both directions: a manifest declaring version: "0.2" while carrying a detached signature block is rejected rather than verified under v0.1 rules. signature is not a v0.2 field at all, since the COSE structure is the signature, so such a document claims the new version while using the envelope with the unauthenticated algorithm identifier and the canonicalize-before-verify step that ADR-0011 moved away from. Accepting it would have made the gate advisory and left the phase 5 deprecation with nothing to enforce. Version 0.1 manifests are unaffected.
[SDK] manifest sign and manifest verify handle version 0.2. sign selects the envelope from the manifest's version field, so a 0.2 manifest is written as a COSE envelope in binary CBOR and a 0.1 manifest is unchanged; there is no flag, consistent with ADR-0011. verify detects the envelope from the CBOR tag rather than the file extension, since guessing a format by filename is the ambiguity the media-type rules exist to remove. Signing a 0.2 manifest requires --output, because binary CBOR down stdout would be corrupted by the terminal. New reference page: COSE envelope.
[SDK] POST /verify/cose accepts a version 0.2 COSE manifest as raw CBOR. The body is the COSE_Sign1/COSE_Sign object itself under Content-Type: application/agent-manifest+cose — the registered media type is the gate, rather than base64 inside a JSON wrapper, since the object is self-contained and the type exists to identify it. Three properties are deliberate: only the exact media type is accepted (a vendor-tree alias, application/cbor, and an absent type are all refused — the server never sniffs the body); no key material crosses the wire, so trust comes from a cose_context configured server-side when the router is built, and an unconfigured endpoint returns UNVERIFIABLE rather than VALID; and the body is bounded before it is parsed, with Content-Length checked when present and the stream capped regardless, because a declared length is attacker-controlled. A malformed or unverifiable envelope is a verdict (200 with a non-VALID result), not a transport error, and parser detail is never reflected back, so the endpoint cannot be used as an oracle for the decoder. Results carry Cache-Control: no-store and X-Content-Type-Options: nosniff. Authentication, authorization and rate limiting remain deployment concerns (spec 5.1: mTLS with the agent's SPIFFE SVID).
[SPEC] version MUST be "0.2", resolving a contradiction inside the specification. Section 2.4's compatibility matrix already planned for 0.2 manifests while the section 3 field table still required "0.1", so a producer following the specification could never emit a manifest the COSE envelope governs. The field table now says "0.2"; "0.1" continues to identify a v0.1 manifest, which stays verifiable. The @context change had described v0.2 as differing from v0.1 "in the @context value alone" — accurate for that change in isolation, but v0.2 as a release also carries the COSE envelope, and ADR-0012 is amended with a note saying so.
[SDK] 0.2 is now a supported manifest version. AM-VEC-007, which used 0.2 as its stand-in for a version from the future, now uses 0.3. Its expected result is unchanged.
[SPEC][SDK] BREAKING: the @context URI moves to https://manifest.agentrust-io.com/v0.2/context.json, and the specification is republished as v0.2 (spec/agent-manifest-spec-v0.2.md, ADR-0012). The v0.1 URI https://agentmanifest.agentrust.io/v0.1/context.json named agentrust.io, a domain this project has never controlled: registered to a third party behind Domains By Proxy, paid through mid-2027, and it has never resolved. Every manifest issued to date was therefore identified under somebody else's name, which is untenable in an identity specification.
Consumers cut over rather than dual-accepting, matching the TRACE v0.2 profile migration (agentrust-io/trace-spec#107) that fixed the identical defect. An implementation that kept honouring the v0.1 URI would keep validating manifests named on a domain we do not own. Manifests already issued under v0.1 stay checkable against the v0.1 specification, which remains published.
No field is added, removed or re-typed by the @context change itself. Serving the context document at the new URL is follow-up work; what changes today is that the domain is ours to serve from. Note that v0.2 as a release is not only this: ADR-0011 assigns the COSE signature envelope to manifest version 0.2 as well, so a v0.2 manifest carries the new @context and is signed as a COSE object. The two changes share a version number.
[0.10.0] — 2026-08-01¶
Exports the SEV-SNP ABI offset table so downstreams can delete the ctypes mirrors they kept solely to read offsets from. Completes what 0.9.0 started: cmcp used its struct as an offset oracle across seven test files, so sharing the parse without sharing the table would only have moved the duplication into test scaffolding, where it would drift silently.
Added¶
[SDK] SNP_OFFSETS and SNP_REPORT_LEN are public. The offsets are the contract consumers build and appraise reports against, and they are now checked against the genuine Azure capture rather than against themselves.
[0.9.0] — 2026-08-01¶
Shares the SEV-SNP report union so cmcp and ca2a can delete four copies of the layout between them, and restores a check that existed only in the copies being deleted: the report's declared sig_algo is now verified before the signature is checked under it. Phase A2 of consolidating TEE verification into this package.
Added¶
[SDK] SnpReport now carries guest_svn, vmpl and signature_algo, and load_snp_cert_chain() is public. Phase A2 of the TEE consolidation: cmcp and ca2a carried four copies of the SEV-SNP report layout between them (two inside cmcp alone), and all four agreed on every offset, so this is a union rather than a reconciliation. The three fields were parsed by the downstream copies and not by this one, which meant a consumer of agent-manifest could not enforce checks those copies enforced.
load_snp_cert_chain() splits a concatenated PEM into (vcek, ask, ark) by shape rather than order: the VCEK is the only EC leaf, and of the two RSA certificates the self-signed one is the ARK. It came from cmcp, which had it and this package did not.
Fixed¶
[SDK] verify_snp_signature() now checks the report's declared sig_algo before verifying. It assumed ECDSA-P384/SHA-384 because that is the only scheme AMD has defined, and verified under it without confirming the report said so. Both downstream copies checked this field; the shared implementation did not, so consolidating onto it would have silently dropped a check. A report declaring anything other than SIG_ALGO_ECDSA_P384_SHA384 now raises rather than being appraised under the wrong scheme.
This surfaced two synthetic fixtures in this repo that left sig_algo at zero, which no AMD processor emits — the genuine capture in tests/vectors/snp/azure_snp_report_redacted.bin carries 1. Both fixtures described a report that cannot exist and are corrected. Same shape of defect as the cmcp TPM fixture found in 0.8.0.
[0.8.0] — 2026-08-01¶
Shares the TPMT_SIGNATURE parse and teaches the quote parser both attest framings, so cmcp and ca2a can delete their copies rather than keep three implementations of the same wire formats in step by hand. Phase A1 of consolidating TEE verification into this package. No change to manifest signing or verification behaviour.
Added¶
[SDK] parse_tpmt_signature() and ParsedSignature are now public, so cmcp and ca2a can stop carrying a copy each. Both had byte-identical implementations of the TPMT_SIGNATURE unwrap that tpm2_quote -s and tpm2-pytss's signature.marshal() produce, differing only in which exception they raised; cmcp's comment already named this as "the piece agent-manifest does not model". It raises TpmVerificationError rather than ValueError, so a downstream migrating off its own copy needs to widen its except clause. struct.error on a truncated buffer is now caught and re-raised as TpmVerificationError, which ca2a handled and cmcp did not.
[SDK] parse_tpm_quote() accepts a size-prefixed TPM2B_ATTEST as well as a bare TPMS_ATTEST. tpm2_quote -m writes the bare form and other producers write the wrapped one, so a verifier that accepts only one rejects genuine quotes from standard tooling. TpmQuote.raw is now always the inner TPMS_ATTEST, and verify_tpm_quote() checks the AK signature over that rather than over its argument — verifying over the outer bytes would have failed a real wrapped quote. For bare input, which is everything the suite previously exercised, both are the same bytes and behaviour is unchanged.
Framing is decided by requiring the magic to appear under one reading or the other, not by the leading bytes alone. The obvious implementation, "magic at offset 0 means bare, otherwise treat the first two bytes as a length", silently reinterprets a blob with a corrupt magic as a framing fault and reports TPM2B_ATTEST size field invalid, which sends whoever is debugging a one-bit corruption to the wrong problem. Two tests caught this and both are kept as regression guards.
Changed¶
[SPEC] Target standards body retargeted from AAIF to CoSAI Working Stream 4, an OASIS Open Project, following the Phase 1 RFC in cosai-oasis/ws4-secure-design-agentic-systems#149. Affects the spec header, section 3.1 (who assigns the canonical @context URL), section 3.2.5 (scanner registry), section 10.1 through 10.3, and the governance set: CHARTER.md, GOVERNANCE.md, MAINTAINERS.md, ANTITRUST.md, CONTRIBUTING.md, ROADMAP.md, README.md. No normative data-model, cryptographic, or conformance change; nothing about how a manifest is signed or verified moves.
Three things did not change mechanically with the rest. The conformance test suite in section 8.2 was described as shipping "alongside the AGT donation to AAIF" and is now decoupled, because AGT's standards destination is governed separately and is not set by this charter. Two AAIF references are retained deliberately: the AAIF Spec Enhancement Proposal (SEP) route in section 6.3 and the MCP (Anthropic / AAIF) row in section 10.4 both describe MCP's own governance home, not this specification's target. And the IP terms are stated as consequences rather than commitments: the OASIS Open Projects IPR Policy requires a CLA plus a patent non-assert on non-trivial contributions, which is stricter than the DCO-only regime in force today, so CHARTER.md section 4 records that it takes effect only on WS4 acceptance and that the founding maintainer's terms under it need counsel sign-off first. Trademark transfer terms are marked to be determined rather than asserted.
Fixed¶
[SDK] parse_tdx_quote_signature() now rejects a quote whose declared lengths overrun the buffer instead of silently parsing a shorter value. Four lengths come from the quote, which is untrusted input: the signature-data size, cert_size, qe_auth_size, and pck_size. Python slicing clamps rather than overreading, so an inflated length previously yielded a short slice and parsing continued against whatever fit. No read was ever out of bounds and the downstream signature check would fail, so this is fail-closed hardening rather than a memory-safety fix, but a verifier should reject a quote that declares 400 bytes and supplies 300 rather than appraise the 300. Found while reviewing the same parse in cmcp#420, which shares the derivation.
[0.7.0] — 2026-07-27¶
Shares the hardware-validated TDX quote-signature parse so the sibling repos can stop carrying their own copies of the offsets, and specifies the v0.2 COSE envelope. No change to manifest signing or verification behaviour.
Added¶
[SPEC] COSE envelope specified for manifest version 0.2: spec/agent-manifest-cose-envelope-v0.2.md, phase 1 of the ADR-0011 migration (#243). COSE_Sign1 (tag 18), or COSE_Sign (tag 98) with two signers for a hybrid Ed25519 + ML-DSA-65 signature so that both entries covering one payload is structural rather than an application rule. Protected header carries alg (-8 / -49, RFC 9053 and RFC 9964), kid, content type (3), and typ (16, RFC 9596); the payload is the RFC 8785 canonical JSON of the manifest, carried inline; the SCITT receipt attaches as receipts (unprotected label 394, RFC 9942 receipts). Media types application/agent-manifest+json (payload) and application/agent-manifest+cose (object), standards-tree, registration pending.
Three v0.1 mechanisms are deleted rather than ported, because each existed only to work around JSON having no defined place for post-signing data: the fixed signed_fields list and its coverage table, the hitl_record.approvals normalization rule, and the transparency_log_entry ordering rule. In v0.2 the payload is what is signed, and approvals, the attestation report, and the receipt all attach in the unprotected header. Hardware attestation binds sha256 of the payload bytes, so there is no longer a set of fields to exclude and keep in sync. No code changes; manifest version 0.1 is untouched and continues to verify under v0.1 section 3.6.
[SDK] parse_tdx_quote_signature() and TdxQuoteSignature expose the validated DCAP v4 signature-section parse (de-nested QE report, QE signature, auth data, PCK chain PEM) so sibling repos can delegate to it instead of reimplementing the offsets. Real quotes nest the QE material under a type-6 QE_REPORT_CERTIFICATION_DATA header; a flat parse reads the QE report six bytes early and rejects every genuine quote, which is what happened in cmcp and ca2a. verify_tdx_quote() now calls the shared parse, so there is one copy of the layout, and two regression tests pin the nested structure.
[0.6.1] — 2026-07-27¶
Closes a crash in verify_manifest() reachable from untrusted input on a default install, and settles the signature-envelope question for v0.2. No change to how manifests are signed or to any existing verification result.
Fixed¶
[SDK] verify_manifest() no longer raises on a post-quantum manifest when the pq extra is absent. pyoqs is optional, so on a default install any manifest declaring ML-DSA-65 or hybrid-Ed25519-ML-DSA-65 reached _require_oqs() and crashed the engine with an uncaught RuntimeError. Since a manifest is untrusted input, a verification endpoint would answer 500 to an attacker-supplied manifest rather than returning a verdict. _require_oqs() now raises AlgorithmUnavailableError (a RuntimeError subclass, so existing callers are unaffected), the engine catches it, records the reason as a warning, and returns UNVERIFIABLE. Not MISMATCH: the verifier has established nothing about a manifest that may be entirely valid, so accusing it of a defect would be wrong. An algorithm identifier outside the registry remains a MISMATCH, rejected by the schema enum before verification runs.
[SDK] A signature block with no algorithm field no longer falls back to Ed25519. The field is REQUIRED by spec 3.6 but sits outside the signing pre-image, and the verifier defaulted a missing identifier to the classical algorithm; it is now a signature.algorithm mismatch. Completes the 0.6.0 downgrade check, which only covered a present-but-weaker identifier.
Documentation¶
[SPEC] ADR-0005 amended after a spec-versus-implementation audit. Three of its statements did not match what shipped: it defined three crypto_profile values (standard, post_quantum, hybrid) where the spec and SDK define two (standard, post-quantum) with hybrid as a signature algorithm rather than a profile; it required the post-quantum profile at "Level 2 and above" where section 8.1 places it at Level 3; and it required an unsupported-algorithm verifier to "raise INCOMPATIBLE_VERSION", which is reserved for unsupported specification versions and is not something a verifier should raise at all. The original text is preserved per the ADR immutability rule, with an amendment section recording each correction. Section 4.2 now states the UNVERIFIABLE requirement normatively.
[SPEC] New section 10.5, SCITT profile mapping. Maps every structural piece of this specification to its RFC 9943 term (Artifact, Subject, Statement, Issuer, Signed Statement, Transparency Service, Receipt, Transparent Statement, Registration Policy, Auditor), which turns "agent-layer profile of SCITT" into a checkable claim and tells an implementer which parts are agent-specific (sections 3.2 to 3.5) and which are inherited. The section also states what the spec deliberately does not restate: OpenSSF Model Signing for the model artifact, SLSA and in-toto for build provenance, SCITT and Sigstore for transparency. Section 10.4 gains an OMS row, and v0.2 gains a line item for an explicit OMS bundle reference in model_identity so a verifier can follow the chain from agent to model publisher instead of trusting an operator-asserted hash.
[SPEC] New ADR-0011: the manifest is a signed document, not a JWT/JOSE profile, accepted. Answers the recurring "why not just a JWT extension?" question on precedent rather than on capability, steelmanning EAT (RFC 9711) rather than dismissing it, and setting against it the choice every comparable multi-artifact provenance standard made: SCITT (RFC 9943) mandates COSE_Sign1, DSSE rejected a JWS profile in writing, C2PA signs with COSE_Sign1_Tagged. The ADR also records a decision this project had never actually made: the envelope is neither JOSE nor COSE but a bespoke canonical-JSON detached signature, which carries both properties DSSE cites as reasons to avoid JWS while lacking a specification anyone else implements.
The accepted decision is that the envelope moves to COSE_Sign1 in spec v0.2. The post-quantum profile is not a blocker, which was the main technical risk: RFC 9964 (Standards Track, May 2026) gives ML-DSA final IANA code points in COSE (ML-DSA-65 = alg -49, AKP key type 7). Migration is sequenced in five phases and gated on the manifest version field, so v0.1 records keep verifying unchanged; tracked in #243. Hybrid is the one construction COSE has no single answer for and is deferred to the v0.2 spec work. Nothing in this release changes how a manifest is signed.
[SPEC] Corrected three factual errors in the spec. Section 2.2 and Section 5 described the manifest signature as "JWS", which it has never been (there is no JOSE dependency in the SDK; the signature is a detached Ed25519 or ML-DSA-65 signature over an RFC 8785 pre-image). Section 10.4 cited EAT as RFC 9528, which is EDHOC; EAT is RFC 9711.
[SPEC] Section 3.6 gained a normative algorithm-binding rule making the 0.6.0 verifier behaviour part of the specification: because the signature block sits outside the pre-image, a verifier MUST cross-check the declared algorithm against the signed crypto_profile, MUST reject a downgrade, and MUST reject an unrecognized algorithm identifier rather than defaulting to Ed25519. Section 10.4 adds rows for RFC 9711 and RFC 9943 positioning Agent Manifest as the agent-layer profile of the SCITT model.
[SDK] docs/index.md FAQ answers "why not specify it as a JWT or JOSE profile?" with the short form of the ADR-0011 argument.
[0.6.0] — 2026-07-26¶
Fixes the CLI that every document described but that nobody could run, and closes a signature-downgrade gap in the verifier.
Fixed¶
[SDK] The documented CLI invocation now works. Every command was nested under a redundant second manifest group, so the real invocation was manifest manifest verify signed.json while the README, the docs site, the PyPI description, and the CLI's own module docstring all printed manifest verify signed.json. Running the published quickstart failed at the first step with Error: No such command 'keygen'. tests/test_cli.py used the nested form, so CI never caught it. Commands are now attached to the top-level group as documented; the nested spelling still works, is hidden from --help, and prints a deprecation warning (removal in 1.0). test_documented_commands_are_top_level guards the surface.
[SDK] verify_manifest() now cross-checks the signed crypto_profile against signature.algorithm and fails closed on a downgrade (spec 4.2). crypto_profile is inside the signing pre-image, but the whole signature block is excluded from it (spec 3.6), so the algorithm identifier could previously be rewritten without disturbing the signed bytes: a manifest declaring the post-quantum profile verified VALID on a classical-only Ed25519 signature. The check runs independently of trusted_keys (the downgrade is a property of the manifest, not of the verifier's key material) and is one-directional: it rejects a signature weaker than the declared profile requires and permits a stronger one, so an issuer dual-signing ahead of the profile flip is not flagged. New conformance vector AM-VEC-020.
Documentation¶
[SDK] docs/getting-started.md: new "Watch it catch a change" step. Shows the two ways verification fails and how they differ — an edit to the signed record (signature fails, signature_verified: false) versus runtime drift against an intact signature (declared-vs-actual binding fails, system_prompt: MISMATCH) — and names the boot-time boundary, pointing at attest_runtime_state() for freshness. All printed values are copied from real runs.
[SDK] docs/api-reference/cli.md is now generated from the CLI by scripts/gen_cli_reference.py instead of hand-written. The hand-written page had drifted into fiction: it documented manifest keygen --out/--print-pub, manifest verify --revocation-url/--min-slsa-level, manifest create --agent-id/--issuer/--model/--ttl-hours, and manifest revoke --crl-file/--key-file, none of which exist, while omitting the options that do. test_cli_reference.py fails if the committed page drifts from the CLI again.
[SDK] CLI help output is readable: \b markers keep the example blocks from being rewrapped into one line, and -o/--output, --enforce-hitl, and --enforce-attestation have help text instead of appearing bare.
[SDK] Corrected CLI examples that could not have worked: docs/operations/key-rotation.md used the non-existent manifest keygen --out ... --print-pub, and docs/index.md plus python/README.md printed manifest verify without --public-key, which fails closed as UNVERIFIABLE and exits 1 rather than the VALID shown.
[SDK] LIMITATIONS.md: document that Azure TDX is not supported for offline attestation (hardware-confirmed). Azure runs TDX behind the Hyper-V paravisor, so the guest gets no signed DCAP quote — only a MAC'd TDREPORT via the vTPM — and rooting that as genuine silicon needs a networked service (Azure MAA). Offline TDX attestation is supported on non-paravisor guests (e.g. GCP C3); on Azure use SEV-SNP (AzureCVMProvider). Azure-MAA TDX support is tracked as a follow-up.
[0.5.0] — 2026-07-21¶
Generalizes the verification API so cmcp and ca2a can delegate their full SNP/TDX/TPM crypto to this package (via PyPI) without changing behavior or rewriting their test fixtures. Backward compatible — all existing functions and signatures are unchanged.
Added¶
[SDK] Generic, algorithm-agnostic certificate-chain verifier verify_cert_chain(chain, trusted_roots, *, root_fingerprint_hash=SHA256) (exported, with CertChainError). Verifies a leaf-first chain by honoring each certificate's own signature algorithm — ECDSA, RSASSA-PSS, or RSA PKCS#1 v1.5 — via x509.Certificate.verify_directly_issued_by, then pins the chain root by fingerprint. This is the shared primitive behind AMD VCEK, Intel PCK, and TPM AK chains; it lets both consumers replace their own chain verifiers (cmcp's synthetic RSA-PKCS1v15 ARK/ASK and ca2a's EC chains both verify through it). The AMD-specialized verify_vcek_chain is unchanged (kept as its hardware-validated specialization).
Changed¶
[SDK] parse_tdx_quote(quote, *, strict=True) gained a strict flag. strict=True (default) keeps enforcing the production layout (version==4, tee_type==0x81); strict=False parses the header/body of an otherwise well-formed quote whose version/tee_type differ (e.g. consumers' synthetic vectors) without asserting production TDX identity. verify_tdx_quote is unaffected and always strict.
[0.4.0] — 2026-07-21¶
Makes agent-manifest the canonical hardware-verification library for the org: SEV-SNP, TDX, and now TPM quote verification live here and are consumed by cmcp and ca2a via this PyPI package rather than duplicated per repo.
Added¶
[SDK] Shared TPM 2.0 quote verifier (agent_manifest._tpm_verify, exported: parse_tpm_quote, verify_tpm_quote, TpmQuote, TpmVerificationError). Fail-closed appraisal of a TPMS_ATTEST quote: magic/type structural check, AK certificate chain to a caller-pinned trusted root, AK signature (ECDSA-P256 or RSA PKCS#1 v1.5 / SHA-256) over the attest blob, and constant-time qualifying-data (nonce) + PCR-digest binding checks. Wired into verify_attestation_chain (dispatch on platform in {"tpm","aws-nitro"}). Ported from ca2a's reference implementation so the three repos share one verifier. Caveat: exercised against synthetic self-consistent vectors; unlike the SEV-SNP/TDX paths it is not yet validated against a real TPM quote (follow-up).
[SDK] Intel TDX DCAP quote verification (agent_manifest._tdx_verify, exported), hardware-validated on a non-paravisor TDX guest (GCP C3). TDXProvider now uses the configfs-TSM tdx_guest provider, which returns a full remotely-verifiable DCAP quote (v4, ECDSA-P256) instead of a bare local TDREPORT. Verification checks the quote's attestation-key signature over the TD report, the QE report binding, the PCK signature over the QE report, and the PCK certificate chain up to the pinned Intel SGX Root CA (embedded; offline). Wired into verify_attestation_chain, which now returns passed=True for a TDX report only when the quote + PCK chain verify. Closes the TDX half of the "shipped the binding without verification" gap (#204/#228); the previous /dev/tdx-guest ioctl path (raw TDREPORT, no signature check, RTMR-extend that never happened) has been removed. Azure TDX (paravisor/vTPM-rooted) remains a follow-up. [SDK] AzureCVMProvider — hardware-attested manifest binding on Azure confidential VMs, validated on live SEV-SNP silicon (Azure DCasv5). Azure runs SNP behind a Hyper-V paravisor, so there is no /dev/sev-guest; the SNP report is read from the vTPM NV index 0x01400001 and the manifest hash is bound through the vTPM (PCR + AK-signed quote), with the AK rooted in silicon by the SNP report + VCEK chain. Auto-selected by provider='auto' on Azure. [SDK] AMD SEV-SNP signature backend (agent_manifest._snp_verify, exported): SNP report parsing, HCL-report splitting, the Azure REPORT_DATA == sha256(runtime_data) binding check, ECDSA-P384 report-signature verification against the VCEK, and VCEK ← ASK ← ARK chain verification (with optional pinned AMD root). Validated against a real SEV-SNP report. [SDK] verify_attestation_chain now performs real hardware-signature verification when VCEK/certificate material is supplied (previously always NOT_IMPLEMENTED); it returns passed=True only once the SNP signature and VCEK chain verify. Without VCEK material it still fails closed.
Changed¶
[SDK] SEVSNPProvider now uses the kernel configfs-TSM interface (/sys/kernel/config/tsm/report, kernel 6.7+) for bare-metal / non-paravisor SNP guests; the previous /dev/sev-guest ioctl path (never hardware-validated, incorrect ABI) has been removed. Hardware-validated on a non-paravisor SEV-SNP guest (GCP N2D, AMD Milan): the manifest digest lands in the guest-controlled REPORT_DATA and the report verifies against the AMD VCEK chain. On Azure use AzureCVMProvider. [SDK] Attestation providers (AzureCVMProvider, SEVSNPProvider, TDXProvider, OPAQUEProvider, TPMProvider) and the chain verifier are now exported from agent_manifest; CLI manifest attest accepts --provider azure-cvm. [SDK] OPAQUEProvider is now explicitly not implemented and fails closed at construction. The OPAQUE managed attestation service is not generally available and the SDK never verified the TRACE claim it would return (no claim-signature or service_measurement check — issue #201 §5); shipping a path that looked verified but was not is worse than none. Use a locally-verifiable provider (SEV-SNP / TDX / Azure CVM) for Level 1+. The prior unverified HTTP flow has been removed.
[0.3.0] — 2026-07-15¶
Security¶
[SDK] Verification can now bind trusted signing keys to authorized issuers. VerificationContext.trusted_key_issuers maps each trusted key_id to the issuer SPIFFE URIs allowed to sign with it; when supplied, a manifest whose signing key is not authorized for its declared issuer is rejected (fail-closed). Opt-in and backward compatible: an omitted value preserves prior behavior, while an explicitly empty map authorizes no issuers.
Added¶
[SDK] Delegation verification is now part of the public API: verify_delegation_chain, verify_hitl_approval, delegation_depth_exceeded, DelegationHopSigner, and HitlApprovalSigner are exported from agent_manifest. Downstream projects (for example agentrust-io/cA2A) call verify_delegation_chain to verify an inbound peer's delegation chain, so the two implementations stay aligned rather than duplicated. No behavior change; these were previously reachable only through the private _delegation module.
[0.2.0] — 2026-06-30¶
Security¶
[SDK] Delegation chain root is now bound to the manifest issuer/agent identity — forged-authority chains are rejected. [SDK] Scope-narrowing enforces constraint-superset, non-increasing ttl_seconds, and non-increasing max_delegation_depth. [SDK] Verification schema-validates the manifest (fail-closed); CLI verify no longer prints bare VALID when artifact bindings were not checked.
Changed¶
[SPEC] SNP/TDX attestation field corrections and provider experimental markers (REPORT_DATA at 0x50); threat-model/levels documentation scoped to what TEE attestation provides.
Fixed¶
[SDK] PrincipalType set reconciled (no service).
Added¶
[SPEC] Memory Checkpoint & Delta Protocol (Section 3.2.6.2) — v0.2 incremental memory binding. - Append-only operation-log (merkle-log) model lets persistent memory evolve across a session and prove the evolution was governed, without re-approving the whole store. - Per-representation leaf canonicalization: key-value, semantic/vector (binds embedding + model id), and graph-RAG (nodes + edges). - A governed checkpoint advance is accepted only with a valid RFC 9162 §2.1.2 consistency proof; an unproven change still triggers v0.1 drift detection (MEMORY_DRIFT_DETECTED) — fail-closed preserved.
[SDK] MerkleTree.consistency_proof + verify_consistency (RFC 9162 §2.1.2) in agent_manifest._merkle. [SDK] agent_manifest._memory_delta: build_memory_tree, MemoryCheckpoint, verify_delta, fold_kv. [SDK] MemoryCheckpointBinding model (memory_root anchor; additive — MemoryBaselineBinding and snapshot_hash semantics unchanged).
[SDK] Export the verification API from the package root, so relying parties and gateways call agent_manifest.verify_manifest() and VerificationContext directly instead of importing the private _verify module (#176).
[SPEC] Document runtime-session binding guidance for gateways, including the signed fields that bind agent_id, artifact hashes, validity windows, delegation handling, and attestation separation (#177).
[0.1.0] — 2026-06-23¶
Stable launch release at Confidential Computing Summit, June 23 2026.
Fixed¶
[SDK] Enforce poisoning_scan.result rules in verifier — bad scan results now correctly fail closed (#167). [SDK] Align Pydantic models, examples, and signing logic to the v0.1 spec (#165). [SDK] Transparency log and signing error paths fully covered; fail-closed verifier restored (#168).
[0.1.0-alpha1] — 2026-06-04¶
Initial developer preview. Launching at Confidential Computing Summit, June 23 2026.
Added¶
[SPEC] v0.1 specification published. - All 10 artifact bindings defined (Sections 3.2.1–3.2.8, 3.4, 3.5) - Hardware attestation binding for TPM, SEV-SNP, TDX, OPAQUE (Section 3.3) - A2A delegation chain with Cedar scope constraint evaluation (Section 3.4) - HITL approval records with hardware-signed approver identity (Section 3.5) - Manifest signature protocol: Ed25519 / ML-DSA-65 / hybrid (Section 3.6) - Revocation and key rotation protocols (Sections 3.7, 3.8) - Standard and post-quantum cryptographic profiles (Section 4) - Verification endpoint specification with error schema (Section 5) - Integration architecture for AGT, cMCP, MCP (Section 6) - Threat model covering 10 threat classes (Section 7) - Conformance levels 0–3 with 197 conformance tests across 5 modules (Section 8) - Regulatory mapping: EU AI Act, DORA, GDPR, HIPAA, PCI-DSS, FedRAMP (Section 9)
[SDK] Python SDK v0.1.0-alpha1 (pip install agent-manifest). - Manifest, ArtifactBindings, and all 10 artifact binding Pydantic models - generate_ed25519, Ed25519Signer for standard-profile signing - verify_manifest, VerificationContext, RevocationStore for verification - Merkle tree computation for RAG corpus and tool manifest catalog hash - RFC 8785 canonical JSON serialization - Hardware provider auto-selection: OPAQUE > SEV-SNP > TDX > TPM > software - CLI: manifest keygen, create, sign, attest, verify, revoke - Post-quantum support via pyoqs: pip install "agent-manifest[pq]" - Verification server: pip install "agent-manifest[server]" - Python 3.11, 3.12, 3.13 support
-
Python 3.11, 3.12, 3.13 support
-
Python 3.11, 3.12, 3.13 support
-
Python 3.11, 3.12, 3.13 support