Skip to main content

11-Step Verification Pipeline

Every intercepted capability call goes through this pipeline before your code runs. A fresh JWT is minted and verified on every call — no token reuse.

The Pipeline

Step-by-Step

StepWhat is checkedError on failure
1-2Decode JWT header + payload, confirm typ = "agent+jwt"token_invalid
3sub matches registered agentIdagent_not_found
4iss matches registered public key thumbprinttoken_invalid
5aud matches the requested capability namecapability_denied
6hostThumbprint in token matches agent's registered Hosttoken_invalid
7Ed25519 signature is validtoken_invalid
8exp/iat temporal check (30s clock skew tolerance)token_expired / token_invalid
9JTI not seen in the 90-second replay windowtoken_replayed
10Agent holds an active grant for this capabilitycapability_denied
11Grant has not expired (expiresAt)capability_denied
11bCall arguments satisfy all grant constraints + required fieldsconstraint_violated

JWT Claims

Every capability call mints a JWT with these claims:

{
"typ": "agent+jwt",
"alg": "EdDSA",
"sub": "<agentId>",
"iss": "<agent JWK thumbprint>",
"aud": "<capability name>",
"hostThumbprint": "<Host JWK thumbprint>",
"jti": "<random 128-bit base64url>",
"iat": 1700000000,
"exp": 1700000060,
"hostname": "<agent hostname>",
"agentName": "<agent name>"
}

The token is:

  • Signed with the agent's Ed25519 private key
  • Single-use — a unique jti per call (replayed within 90s = rejected)
  • Short-lived — 60-second TTL
  • Scopedaud must exactly match the capability name

JTI Replay Protection

The jti (JWT ID) is cached for 90 seconds. If the same jti appears again within that window, the call is rejected with token_replayed. This prevents replay attacks even if an attacker captures a valid token.

By default, JTI tracking is in-memory. For multi-process deployments, inject a Redis adapter.

Auth Overhead

The entire pipeline runs in-process via Node.js Web Crypto. Typical warm-path overhead is under 1ms per call:

const stats = chain.getStats();
// stats.authOverhead → { avgMs: number, maxMs: number }

Every audit entry records authOverheadMs so you can monitor the cost of the auth pipeline.