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
  • Scoped — aud 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.