Agent passports
An agent passport (CAP+JWT) is a short-lived signed credential that answers: “Who is this agent, what is it allowed to do, and who authorized it?” Think of it like a badge at a secure facility. The badge was issued by a known authority (your Vane instance), has a photo (the agent’s SPIFFE identity), lists what rooms it can access (scopes), and expires at the end of the day. A guard at any door can verify the badge by checking the authority’s public seal — they don’t need to call the badge office.What a passport encodes
jti— unique ID for revocation trackingcounsel.scopes— what the agent is authorized to do (see scope format below)counsel.delegationChain— the full chain from authorizing organization to the agent
Scope format
Scopes usecategory:name format. Matching is evaluated left-to-right:
When the MCP middleware verifies
tool:search, it checks whether any scope in the passport’s scopes array covers tool:search. A passport with tool:* passes; one with only tool:summarize does not.
Passport TTL
The default TTL is 3600 seconds (1 hour). You can request up to 86400 seconds (24 hours) at issuance. Shorter is safer — expired passports are rejected outright by any verifier.Offline verification
A verifier holding the CA public key verifies a passport in this order:- Parse the JWT (three base64url segments separated by
.) - Check
alg = EdDSA— no algorithm confusion possible - Check
typ = CAP+JWT— prevents replay with SVID tokens (which usetyp = JWT) - Verify the Ed25519 signature over
header.payload - Check
exp— passport must not have expired - Check
nbf— passport must have taken effect - Check
audincludescounsel:passport:v1 - Check
issis a valid SPIFFE URI - Check
subis a valid SPIFFE URI - Validate the
counselobject (version, scopes, delegationChain) - Check
delegationChaintail equalssub - If a tool name was provided, check scopes cover
tool:<name>
Attestation chains
An attestation chain is the auditable log of everything an agent has done. Think of it as a ledger: each page (record) is numbered, dated, contains the content, and is signed by the same authority. If any page is altered, the numbering breaks.Record structure
hash is computed as:
index is included so you can’t swap two authentic records without breaking their hashes. canonicalize sorts object keys recursively before serializing, so the same logical object always produces the same bytes regardless of insertion order.
The signature is Ed25519 over the hash (not over the full preimage). Signing the hash keeps verification fast regardless of payload size.
Merkle tree
All record hashes are organized into a binary Merkle tree. This lets anyone verify a single record’s inclusion in O(log n) without downloading the full chain:- Start with the record’s
hash - Hash it with each sibling in the proof (sibling-left means
SHA-256(sibling + hash), sibling-right meansSHA-256(hash + sibling)) - If the final result equals the root, the record is in the chain
GET /v1/verify and GET /v1/proof/:index is always the same value. An external auditor can checkpoint the root at a point in time and later verify any record against it.
Delegation chains
A delegation chain answers: “On behalf of whom did this agent act, and who authorized that?”Simple delegation
When researcher-1 acts on behalf of acme (the most common case), the chain is just two elements:Multi-hop delegation (RFC 8693)
When a sub-agent acts on behalf of a company via an intermediate agent, the chain grows:act claim in the JWT encodes the same chain as a nested object:
Revocation
Passports can be revoked by theirjti (unique ID). Revocation is immediate on the Vane server but does not invalidate tokens already held by verifiers — that’s the inherent trade-off of offline verification.
How revocation works
POST /v1/passports/:jti/revoke— records the revocation in the databaseGET /v1/passports/revoked— returns the full revocation list for a companyGET /v1/ocsp/:jti— status check for a specific passport (with Ed25519-signed response)
Cache-Control: public, max-age=300). A verifier that calls the OCSP endpoint is getting a cryptographically verified status, not just a database lookup.
Passport rotation
POST /v1/agents/:agentId/passport/rotate is the controlled credential refresh path. It requires the current valid passport in the Vane-Passport header, revokes the old passport (marking it as reason: "rotated"), and issues a new one with the same scopes and TTL. This is the correct way to refresh credentials without a gap in authorization.
SPIFFE identities
Every entity in Vane has a SPIFFE ID — a URI that encodes its position in the trust hierarchy.
The trust domain (
vane.local by default) is controlled by the SPIFFE_TRUST_DOMAIN environment variable. All companies in one Vane deployment share a trust domain.