Skip to content

ADR-0010: Biscuit tokens for capability authorization

  • Status: Accepted
  • Date: 2026-04-12

Context

Capability invocations need bearer credentials. Agent contexts add requirements web-auth doesn't have: - Attenuation: a parent agent delegates a narrower capability to a child agent. - Offline verification: agents on the edge verify tokens without network round-trip. - Capability-scoped: tokens carry their own authorization, not a session lookup.

Options:

  1. JWT (signed). Ubiquitous. No attenuation. Revocation painful. No built-in authorization logic.
  2. PASETO. Better crypto defaults than JWT. Same limitations otherwise.
  3. Macaroons. Attenuatable via caveats. Original capability-token design. Active research but small ecosystem.
  4. Biscuit. Modern macaroon-descendant. Attenuable, offline-verifiable, has a Datalog-based authorization language for expressing caveats. Rust-native library (biscuit-auth).
  5. GNAP tokens. IETF draft for advanced delegation. Complex, new, poor agent-context tooling.
  6. OAuth2 access tokens. Session-bound. Not capability-first. Poor fit for agent-to-agent delegation.

Decision

Biscuit is the default capability token format.

  • trails-identity issues and verifies biscuit tokens.
  • Biscuit root keys per-app, env-configured (TRAILS_BISCUIT_KEY).
  • Caveats encode scoping: principal DID, capability ID whitelist, expiration, audience, resource restrictions.
  • Attenuation supported: a handler can issue an attenuated biscuit to a downstream agent.
  • Offline verification is default; no network round-trip per invocation.

JWT / VC-JWT remain supported for interop (e.g., VC-based preconditions) but aren't the primary capability token.

Consequences

Positive

  • Attenuation is a first-class operation. Agent chains work cleanly.
  • Offline-verifiable. No central auth service bottleneck.
  • Datalog caveats express agent-relevant constraints (time, capability, resource) declaratively.
  • Rust-native library (biscuit-auth) — zero FFI cost in kernel.
  • Small, audited spec.

Negative

  • Less known than JWT. Some devs will be unfamiliar. Mitigated by docs and examples; framework hides most details.
  • Revocation still requires a central check for time-critical cases. Mitigated by short-lived tokens (default 1h) + refresh flow; revocation list optional for strict scenarios.
  • Ecosystem smaller than JWT. Fewer client libraries across languages. Mitigated by the framework issuing tokens on behalf of apps; most app code never touches biscuit internals.

Non-consequences

  • VCs are orthogonal — they carry claims (not capability authority) and are verified independently.
  • DIDs are orthogonal — they identify principals; biscuits authorize actions.

Revisit conditions

  • If biscuit library maintenance stalls, evaluate porting or switching to SPIFFE SVIDs or raw attenuated JWTs.
  • If a dominant agent-token standard emerges (e.g., an IETF work product), adopt it as an alternative and let biscuit remain as a default for new projects.

Update (2026-04-12)

Per ADR-0013, the primary capability mandate is now an ACT token (draft-nennemann-act-00). Biscuit is scoped to attenuation / chain-delegation below a parent ACT — short-lived, attenuated session tokens issued by a handler to a downstream agent. The Datalog caveat story and offline-verification properties that motivated the original decision still apply in that narrower role. Apps that don't need chain-delegation can operate with ACT alone.

Verification bounds (security hardening). Biscuit Datalog caveat evaluation MUST be bounded at the kernel verifier to prevent crafted-token CPU exhaustion: maximum 1000 facts materialized, maximum 128 evaluation depth, maximum attenuation-chain length 5, per-verification wall-clock budget 1 ms. Tokens that exceed any bound are rejected at parse/verify time with BiscuitBoundsExceeded; the bound is not softened by policy. These bounds are part of the framework surface — not adapter-configurable — so hostile tokens behave identically across deployments.