Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.vane.build/llms.txt

Use this file to discover all available pages before exploring further.

Token type: CAP+JWT

A Vane Agent Passport is a standard JWT with typ: "CAP+JWT". The type string is distinct from SVID tokens (typ: "JWT") to prevent replay attacks across token types. Any JWT library that supports EdDSA can parse a CAP+JWT. The Vane-specific logic is entirely in the counsel claim object and the scope matching rules.
{
  "alg": "EdDSA",
  "typ": "CAP+JWT",
  "kid": "a1b2c3d4e5f60000"
}
ClaimValueDescription
alg"EdDSA"Hard-coded. No algorithm confusion possible.
typ"CAP+JWT"Distinguishes passports from SVIDs.
kidstring16-hex-char key ID derived as SHA-256(SPKI DER)[0:16].

Payload claims

{
  "iss": "spiffe://vane.local/ca",
  "sub": "spiffe://vane.local/company/acme/agent/researcher-1",
  "aud": ["counsel:passport:v1"],
  "jti": "550e8400-e29b-41d4-a716-446655440000",
  "iat": 1751325600,
  "exp": 1751329200,
  "nbf": 1751325600,
  "counsel": {
    "v": 1,
    "agentId": "researcher-1",
    "org": "acme",
    "orgSpiffeId": "spiffe://vane.local/company/acme",
    "scopes": ["tool:*", "attest:write"],
    "delegationChain": [
      "spiffe://vane.local/company/acme",
      "spiffe://vane.local/company/acme/agent/researcher-1"
    ],
    "delegationId": "cb6a2a5e-21ad-4d2c-80c9-d37516f276ab"
  }
}

Standard JWT claims (RFC 7519)

ClaimTypeDescription
issstringSPIFFE ID of the issuing Vane CA: spiffe://{trustDomain}/ca.
substringThe agent’s SPIFFE ID. This is the credential subject.
audstring[]Must include "counsel:passport:v1". Prevents replay across audiences.
jtistringUUID v4. Unique passport identifier. Used for revocation tracking.
iatnumberUnix seconds. When the passport was issued.
expnumberUnix seconds. When the passport expires.
nbfnumberUnix seconds. Before this time, the passport is not valid. Always equals iat at issuance.

Vane-specific claims

All Vane-specific claims are namespaced under the counsel object to avoid collision with JWT extensions.
ClaimTypeDescription
counsel.v1Schema version. Verifiers MUST reject unknown versions.
counsel.agentIdstringHuman-readable agent identifier.
counsel.orgstringThe issuing organization’s name (company ID).
counsel.orgSpiffeIdstringThe issuing organization’s SPIFFE ID.
counsel.scopesstring[]Authorization scopes. Non-empty array. See scope rules below.
counsel.delegationChainstring[]Ordered SPIFFE IDs from authorizing org to the agent. The last element MUST equal sub.
counsel.delegationIdstringOptional. jti of the RFC 8693 token this passport was derived from. Present when the passport was issued from a delegation.

Scope format

Scopes use category:name format. Matching is evaluated left-to-right against the scopes array:
PatternMatches
*Any scope
cat:*Any scope whose category is cat
cat:nameExactly cat:name
Standard scope categories:
CategoryPurpose
toolMCP tool calls (e.g., tool:web-search, tool:*)
attestAttestation writes (attest:write)
resourceResource access (resource:read)

Verification steps

Verification is performed in this order. A failure at any step terminates verification immediately.
StepCheckError code on failure
1Parse — split by ., decode header and payload JSONMALFORMED_TOKEN
2Algorithm — header.alg must be "EdDSA"ALGORITHM_MISMATCH
3Token type — header.typ must be "CAP+JWT"WRONG_TOKEN_TYPE
4Signature — Ed25519 verify over header.payload using CA public keySIGNATURE_INVALID
5Expiry — exp must be in the futureTOKEN_EXPIRED
6Not-before — nbf must be in the past (if present)TOKEN_NOT_YET_VALID
7Audience — aud must include "counsel:passport:v1"AUDIENCE_MISMATCH
8Issuer — iss must be a valid SPIFFE URIINVALID_ISSUER
9Subject — sub must be a valid SPIFFE URIINVALID_SUBJECT
10Vane claims — counsel must be a non-null objectMALFORMED_CLAIMS
11Version — counsel.v must be in supported versionsUNSUPPORTED_VERSION
12Scopes — counsel.scopes must be a non-empty arrayMALFORMED_CLAIMS
13Chain — counsel.delegationChain must be non-empty, tail must equal subCHAIN_INCOHERENT
14Scope check — if a tool name was provided, scopes must cover tool:<name>SCOPE_DENIED

Error codes

CodeMeaning
MALFORMED_TOKENNot three .-separated segments, or base64url decode failed.
ALGORITHM_MISMATCHalg is not EdDSA.
WRONG_TOKEN_TYPEtyp is not CAP+JWT.
SIGNATURE_INVALIDEd25519 signature does not verify.
TOKEN_EXPIREDexp is in the past.
TOKEN_NOT_YET_VALIDnbf is in the future.
AUDIENCE_MISMATCHaud does not include "counsel:passport:v1".
INVALID_ISSUERiss is not a valid SPIFFE URI.
INVALID_SUBJECTsub is not a valid SPIFFE URI.
UNSUPPORTED_VERSIONcounsel.v is not 1.
MALFORMED_CLAIMScounsel object is missing or malformed.
CHAIN_INCOHERENTdelegationChain tail does not equal sub.
SCOPE_DENIEDRequested tool scope not covered by any granted scope.
PASSPORT_REVOKEDServer-side check found the jti in the revocation list.
PASSPORT_REVOKED is only returned by server-side verification (POST /v1/passport/verify). Offline verification via @vane.build/mcp-middleware does not check revocation — use GET /v1/ocsp/:jti for that.

AttestationReceipt

Every successful verification produces an AttestationReceipt. This is not a signature — it is a transparency record produced by the verifier. It can be logged and stored independently of the passport.
interface AttestationReceipt {
  v: 1;
  type: 'VaneAttestationReceipt';
  passportId: string;        // jti — reference the original passport by ID
  agentId: string;
  agentSpiffeId: string;
  org: string;
  orgSpiffeId: string;
  tool: string;              // which tool was called
  scopeGranted: string;      // which scope in the passport covered this call
  delegationChain: string[];
  issuedBy: string;          // iss — which Vane CA signed the passport
  passportIssuedAt: string;  // ISO 8601
  passportExpiresAt: string; // ISO 8601
  verifiedAt: string;        // ISO 8601 — when this receipt was produced
  verifier: string;          // "vane-server/0.1.0" or "@vane.build/mcp-middleware@0.1.0"
}