Skip to content

Verification

Verification is the step where a relying party (the service deciding whether to trust an agent) checks a manifest: is the signature from an issuer it trusts, has the manifest expired or been revoked, and do the recorded artifacts match what it observes. This page documents the function that runs those checks and the optional web endpoint that exposes them.

The core verification engine and FastAPI router. See Tutorial: Server-side verification for usage examples.

Public API

For gateway and runtime-session binding, call the package-root export rather than the private _verify module:

from agent_manifest import RevocationStore, VerificationContext, verify_manifest

verify_manifest() is the supported high-level entry point. VerificationContext.trusted_keys maps an issuer key_id (the SHA-256 hex of the public key bytes) to its base64url-encoded Ed25519 public key, the form returned by Ed25519KeyPair.public_b64url(). A consumer that holds raw public key bytes must base64url-encode them before populating trusted_keys. VerificationContext.trusted_key_issuers maps the same key_id to the issuer URIs that key may sign for (spec section 5.3.3). It is required for VALID: without it the result is UNVERIFIABLE, and a key not authorized for the manifest's issuer is MISMATCH. agent_manifest.signing_pre_image() is the v0.1 signature pre-image: signers and verifiers share it for the exact RFC 8785 canonical byte sequence, including the hitl_record.approvals normalization, so a relying party never reconstructs the pre-image itself. It does not apply to v0.2 manifests. A v0.2 manifest has no field allowlist and no approvals normalization at the payload level; a verifier checks the COSE Sig_structure over the payload bytes exactly as received (agent_manifest.verify_cose_manifest(), built on agent_manifest.cose_payload() for the producer side), with hitl_record.approvals carried in the unprotected header rather than normalized out of the signed payload.

Core function

verify_manifest

verify_manifest(manifest: Union[dict[str, Any], bytes], context: VerificationContext, revocation_store: 'RevocationStore', *, _envelope: Optional[CoseVerification] = None) -> VerificationResult

Core verification engine - hosting-model agnostic and fail-closed.

Checks version compatibility, signature, expiry, revocation, artifact hashes, delegation chain, and HITL. Returns a VerificationResult with per-field status and mismatch details.

Accepts either envelope, selected by what it is given (ADR-0011):

  • A dict is a version 0.1 manifest carrying a detached signature block, verified over the RFC 8785 pre-image exactly as it always has been.
  • bytes are a version 0.2 COSE envelope (COSE_Sign1 or COSE_Sign). The signature is checked over the payload as received, and receipts, attestation, and approvals are read from the unprotected header after the signature is settled.

Fail-closed semantics (spec 5.3 - VALID requires a valid signature):

  • A manifest with an unsupported (or missing) version returns INCOMPATIBLE_VERSION without further processing (spec 2.4).
  • A manifest without a signature block returns SIGNATURE_MISSING.
  • A signed manifest verified without any trusted_keys in the context returns UNVERIFIABLE - never VALID.
  • Issuer key authorization (spec 5.3.3) completes before the signature is verified, on both envelopes. Without trusted_key_issuers it cannot complete and the result is UNVERIFIABLE; a key not authorized for the manifest's issuer is MISMATCH.
  • A delegation chain that cannot be verified (no delegation_public_keys) is marked UNVERIFIABLE and the overall result is UNVERIFIABLE (spec 3.4.1 / 5.2).
  • A delegation chain that verifies signatures and scope narrowing but carries a non-empty scope_grant.constraints (a Cedar statement this verifier does not parse or evaluate) is also marked UNVERIFIABLE, not VALID (spec 3.4.1 / 5.2).
  • enforce_hitl=True with no hitl_record in the manifest is a failure (HitlResult.MISSING and a non-VALID overall result).
  • An approval is never APPROVED unless its own signature verifies with a trusted approver key and binds the current manifest ID and scope.

The _envelope parameter is internal: it carries an already-appraised COSE envelope into the shared pipeline and is not part of the public API.

Context

VerificationContext

Bases: BaseModel

Runtime artifact hashes and keys provided by the trusted component.

Results

VerificationResult

Bases: BaseModel

OverallResult

Bases: str, Enum

FieldsVerified

Bases: BaseModel

FieldResult

Bases: str, Enum

DelegationResult

Bases: str, Enum

HitlResult

Bases: str, Enum

MismatchDetail

Bases: BaseModel

EvidencePack is an optional reference (trace id, signer, hash, and URI) to an externally retained evidence pack that a verifier can record alongside a result.

EvidencePack

Bases: BaseModel

TRACE envelopes and evidence packs

EvidencePack above is only a reference to a pack. To appraise the pack itself, and the per-tool-call TRACE envelopes inside it, use:

from agent_manifest import verify_evidence_pack, verify_trace_envelope

A TRACE envelope (spec §6.3.2) is signed by a TEE-sealed key over the RFC 8785 canonical form of every field except signature. The envelope carries no algorithm or key id of its own, so the caller supplies both; trusted_keys uses the same key_id → base64url public key mapping as VerificationContext. An evidence pack (spec §5.2.1) instead carries a detached pack_signature object in the form of §3.6, so hybrid signatures work there but not on envelopes.

Read admissible, not just status. A TRACE reporting manifest_verification_result: MISMATCH or EXPIRED can have a perfectly valid signature (the runtime honestly recorded a bad state), but spec §6.3.2 says it "MUST NOT be accepted as evidence of a valid tool call for regulatory reporting purposes". verify_trace_envelope() returns status=VERIFIED with admissible=False in that case.

Passing manifest= additionally enforces the §6.3.2 hash-conflict rule (SCHEMA F-21): if the envelope's policy_hash differs from the manifest's artifacts.policy_bundle.hash, the envelope must declare MISMATCH. One that claims VALID over a conflicting hash is a spec violation and fails.

Verification is fail-closed: a missing key, an unknown algorithm, or a build without the [pq] extra yields UNVERIFIABLE, never VERIFIED.

verify_evidence_pack() also appraises the section 5.2 verification_result inside the pack. The pack signature proves who assembled the pack, not what the result says, so the pack is VERIFIED only when that result is VALID and its manifest_id names the pack's manifest. Its verification_signature is a bare string signed by the attestation service over verification_result_pre_image(); pass result_key_id (and result_algorithm for ML-DSA-65) to check it. Without result_key_id it is not checked, verification_result_signature_verified stays False, and the result carries a verification_result_signature_not_appraised warning. signature_verified refers to the outer pack_signature only.

verify_trace_envelope

verify_trace_envelope(envelope: dict[str, Any], *, trusted_keys: Optional[dict[str, str]] = None, key_id: Optional[str] = None, algorithm: str = 'Ed25519', manifest: Optional[dict[str, Any]] = None) -> TraceVerificationResult

Verify one TRACE envelope's signature and manifest binding (spec 6.3.2).

Parameters:

Name Type Description Default
envelope dict[str, Any]

The TRACE envelope dict.

required
trusted_keys Optional[dict[str, str]]

key_id (sha256 hex of raw public key bytes) -> base64url public key. The envelope carries no key id of its own.

None
key_id Optional[str]

Which trusted key signed this envelope. Optional when exactly one key is trusted.

None
algorithm str

"Ed25519" or "ML-DSA-65". Spec 6.3.2 types the envelope signature as a bare string, so hybrid is not expressible.

'Ed25519'
manifest Optional[dict[str, Any]]

When supplied, enforces the spec 6.3.2 hash-conflict rule (SCHEMA F-21) against artifacts.policy_bundle.hash.

None

Returns:

Name Type Description
A TraceVerificationResult

class:TraceVerificationResult. Check admissible before using

TraceVerificationResult

the envelope as evidence -- a verified signature alone is not enough.

verify_evidence_pack

verify_evidence_pack(pack: dict[str, Any], *, trusted_keys: Optional[dict[str, str]] = None, expected_pack_hash: Optional[str] = None, trace_key_id: Optional[str] = None, trace_algorithm: str = 'Ed25519', verify_envelopes: bool = True, result_key_id: Optional[str] = None, result_algorithm: str = 'Ed25519') -> EvidencePackVerificationResult

Verify an evidence pack's pack_signature, its embedded verification result, and its TRACE envelopes.

Parameters:

Name Type Description Default
pack dict[str, Any]

The evidence pack document (spec 5.2.1).

required
trusted_keys Optional[dict[str, str]]

key_id -> base64url public key, covering the pack's TEE-sealed key, the envelope signing key and, when result_key_id is given, the attestation service key.

None
expected_pack_hash Optional[str]

When supplied (e.g. the pack_hash carried in a verification result's evidence_pack reference), it is compared against the recomputed hash.

None
trace_key_id Optional[str]

Key id for the envelopes inside the pack.

None
trace_algorithm str

Envelope signature algorithm.

'Ed25519'
verify_envelopes bool

Set False to appraise only the pack signature.

True
result_key_id Optional[str]

Key id of the attestation service that signed the embedded section 5.2 result. When given, its verification_signature MUST be present and verify. When omitted it is not appraised, verification_result_signature_verified stays False, and a verification_result_signature_not_appraised warning is added.

None
result_algorithm str

"Ed25519" or "ML-DSA-65". Spec 5.2 types verification_signature as a bare string, so hybrid is not expressible.

'Ed25519'

Returns:

Name Type Description
An EvidencePackVerificationResult

class:EvidencePackVerificationResult. status is VERIFIED

EvidencePackVerificationResult

only when the pack signature verifies, the embedded result says

EvidencePackVerificationResult

VALID for this pack's manifest, its signature verifies when a

EvidencePackVerificationResult

result_key_id was given, any supplied pack_hash matches, and

EvidencePackVerificationResult

(when checked) every envelope is admissible.

TraceVerificationResult dataclass

Appraisal of one TRACE envelope.

admissible is the question a relying party actually asks: may this record be used as evidence of a valid tool call? That needs a verified signature and a manifest_verification_result outside :data:INADMISSIBLE_RESULTS. A perfectly-signed envelope reporting MISMATCH is authentic and inadmissible at the same time -- the signature proves the runtime honestly recorded that the policy hash did not match, which is exactly the case spec 6.3.2 excludes from reporting.

EvidencePackVerificationResult dataclass

Appraisal of an evidence pack and, optionally, the TRACEs inside it.

signature_verified describes the outer pack_signature only. verification_result_signature_verified is True only when the embedded section 5.2 result's verification_signature was checked against a caller-named key (result_key_id) and verified.

TraceStatus

Bases: str, Enum

Outcome of appraising a TRACE envelope or evidence pack.

compute_pack_hash() returns the spec §5.2.1 pack_hash: the SHA-256 of the pack's canonical bytes excluding pack_signature. trace_signing_pre_image() and evidence_pack_pre_image() are the shared pre-image functions; producers and verifiers MUST both use them so the byte sequences match.

compute_pack_hash

compute_pack_hash(pack: dict[str, Any]) -> str

Return "sha256:<64-hex>" over the pack's canonical bytes (spec 5.2.1).

trace_signing_pre_image

trace_signing_pre_image(envelope: dict[str, Any]) -> bytes

Return the RFC 8785 canonical bytes a TRACE signature covers.

Every field except signature itself. Both producers and verifiers MUST call this so the byte sequences are identical.

evidence_pack_pre_image

evidence_pack_pre_image(pack: dict[str, Any]) -> bytes

Return the RFC 8785 canonical bytes pack_signature covers.

Every field except pack_signature, which spec 5.2.1 appends to the document after hashing and signing.

verification_result_pre_image

verification_result_pre_image(verification_result: dict[str, Any]) -> bytes

Return the RFC 8785 canonical bytes a section 5.2 verification_signature covers.

Spec 5.1.2 rule 4 requires the signature to cover the whole result, so this is every field except verification_signature itself, the same construction as :func:trace_signing_pre_image.

Revocation

RevocationStore is the revocation lookup a verifier consults during verify_manifest(); the default is in-memory, and production deployments back it with a persistent store. RevocationRecord is a single revocation entry: which manifest was revoked, when, why, and by whom.

RevocationStore

In-memory revocation store. Production should use a persistent backend.

RevocationRecord

Bases: BaseModel

FastAPI router

create_router

create_router(manifest_store: dict[str, dict[str, Any]], revocation_store: RevocationStore, cose_context: Optional[VerificationContext] = None) -> Any

Return a FastAPI APIRouter with /verify and /revocation-status endpoints.

Parameters:

Name Type Description Default
manifest_store dict[str, dict[str, Any]]

Dict mapping manifest_id -> manifest dict.

required
revocation_store RevocationStore

Revocation store instance.

required
cose_context Optional[VerificationContext]

Trust configuration for POST /verify/cose, held by the server rather than accepted from the caller. Omit it and that endpoint is fail-closed: every result is UNVERIFIABLE, never VALID, exactly as GET /verify behaves without keys.

None