AI Agent Security — Interactive Lab
As AI agents proliferate — spawning sub-agents, calling external APIs, acting on behalf of humans — the industry is hitting a wall: no standard way to prove which agent did what, on whose behalf, and with what authority. This lab implements fourteen IETF and OpenID standards that, together, close every gap. Pick a scenario below.
Why Agent Identity Matters
As AI agents proliferate — spawning sub-agents, calling external APIs, acting on behalf of humans — the industry is hitting a wall: no standard way to prove which agent did what, on whose behalf, and with what authority. Existing identity primitives were designed for services, not for autonomous reasoning processes that delegate to one another at runtime.
Every team asks this first. Here's what each existing approach provides — and the specific gap it leaves for AI agents.
| Existing approach | What it gives you | What it doesn't solve for agents |
|---|---|---|
| OAuth Bearer token Authorization: Bearer <token> |
Authorization for an action or scope on one AS | Ambient authority — stolen token = full access until expiry. No per-request proof. No delegation chain: the API sees the token but can't tell if an agent or a human used it, or which sub-agent in a chain called it. |
| mTLS alone X.509 client cert |
Transport-layer identity; proves the caller holds a private key | Cert identity is service-level (e.g. "my-app"), not agent-level. All agent instances share the same cert. No delegation trail — the sub-agent cert says nothing about which orchestrator spawned it. |
| API keys / secrets sk-abc123 / HMAC |
Service-to-service authentication; simple to implement | Static, long-lived, broadly scoped. Shared across all agent instances — a compromised key is service-level blast radius. No per-request binding, no chain, no revocation without rotating every caller. |
| Cloud IAM roles AWS IAM / GCP SA / Azure MI |
Cloud-native identity; short-lived creds auto-rotated by the platform | Cloud-scoped — a GCP service account cannot natively authenticate to AWS. Agent process inherits the workload IAM role; no way to distinguish orchestrator from sub-agent or carry delegation context across clouds. |
| K8s Service Accounts / SPIFFE SVID spiffe://cluster/ns/sa |
Platform identity; cryptographic attestation of the running workload | SPIFFE identifies the workload, not the individual agent. No delegation semantics — SPIRE has no concept of "agent A authorised agent B to act on behalf of user C". Stops at service boundary, no application-layer chain. |
These are not theoretical. Each represents a real class of incident or compliance gap that enterprises encounter when deploying autonomous AI agents at scale.
Agents inherit their host workload's IAM role — which was sized for the workload, not the task. A summarisation agent running inside an "admin" workload can read, write, and delete with full admin authority — even if the task only needed read access to one document. No current framework reduces scope automatically at agent spawn time.
Agents commonly claim "I am acting on behalf of user@example.com" in request headers or metadata — but there is no cryptographic proof of this claim. The downstream API can't verify whether the human actually authorised this action, whether the agent has exceeded the scope the human intended, or whether the delegation has since been revoked.
When an orchestrator spawns sub-agents, it typically passes its own credentials or tokens. The sub-agent receives the same scope as the orchestrator — delegation doesn't reduce authority, it propagates it unchanged. This creates a privilege escalation path: a compromised sub-agent gains the full authority of every agent above it in the chain.
After an incident, the audit log shows caller=my-app — it does not show which agent instance, which model version, which prompt template, or which human initiated the chain. There is no cryptographic way to prove which reasoning process issued the API call. Attribution of AI-driven actions is currently impossible without agent-level identity.
In multi-tenant agent platforms, agents serving different customers often run inside the same workload and share the same cloud identity. There is no identity-layer boundary between tenant A's agent and tenant B's agent — isolation relies entirely on application code correctness, not on cryptographic enforcement. A prompt injection or context leak can expose tenant A's tool calls, credentials, or outputs to tenant B's agent if the application layer fails.
The industry has not been standing still. These are real approaches teams are using today. Each solves part of the problem — none addresses agent-level cryptographic identity.
| Approach | What it solves | Where it stops | What agent-specific identity adds |
|---|---|---|---|
| Tool / function calling frameworks OpenAI function_call, Anthropic tool_use, LangChain tools |
Structured tool invocation; the model declares which tool it wants to call | Tools are defined per session, not per agent identity. Any agent (or attacker with model access) can call any tool in the session — there is no per-agent-instance credential binding | AgentToken bound to agent's EC key; tool calls carry per-request agent-proof+jwt — the tool server verifies which specific agent is calling, not just which session |
| OAuth 2.0 scopes for agents access_token with scope=read:documents |
Limits what a token can do; coarse-grained access control at the API level | Scopes describe what a token can do — not which agent is using it. All agent instances share one token; scope doesn't encode the delegation chain or reduce with each hop | AgentChain JWT encodes the full delegation path with scope reduction at each hop; the downstream API can verify the entire authorisation lineage, not just the final token scope |
| Observability / tracing LangSmith, LangFuse, OpenTelemetry |
Reconstructs what happened; correlates spans across agent calls; useful for debugging | Traces are collected, not attested — any log-line can be forged or omitted. Tracing depends on instrumentation completeness; it cannot prove that a logged action actually happened from that agent | AgentChain and AgentProofToken are cryptographically signed — audit records are tamper-evident by construction, not reconstructed from logs after the fact |
| Service mesh (Istio / Linkerd) mTLS + SPIFFE SVID at the sidecar |
Transport-layer mutual authentication; proves the pod is who it claims to be | Identity is at the service level — all agents inside a pod share the same SVID. Mesh identity doesn't survive TLS termination at a load balancer; no delegation semantics at the application layer | AgentToken operates at the application layer, above TLS — it survives LB termination and identifies the individual agent within the workload, not just the workload itself |
| Cloud IAM conditions AWS IAM condition keys, GCP IAM conditions |
Fine-grained access control; can restrict which resources a role can access based on request attributes | Cloud-scoped — conditions on a GCP IAM policy don't apply to an AWS API call. Conditions apply to the workload role, not to a specific agent instance. No cross-cloud or cross-org delegation | SPIFFE Client Auth + Identity Chaining bridges cross-domain calls with preserved agent identity; CB4A adds per-credential governance independent of cloud IAM |
- Art. 9 — Risk management system must document and monitor AI system behaviour; agent identity records are evidence
- Art. 13 — Transparency: outputs of high-risk AI must be traceable to the specific AI system and version that produced them
- Art. 14 — Human oversight: systems must be designed so a human can verify what the AI did and when
- Govern 6.2 — Transparency and accountability: AI actors must be identifiable and accountable for AI system outputs
- Map 1.6 — Identify AI risks: autonomous delegation without identity is a first-class risk category
- Measure 2.5 — Monitor AI system behaviour: cryptographic audit trails are the mechanism for trustworthy monitoring
- SOC 2 CC6 (Logical Access) — Agent API calls to external services are in scope; without per-agent identity, auditors cannot verify least-privilege enforcement
- GDPR Art. 5(2) — Accountability: data controllers must demonstrate compliance; AI agent actions on personal data require attributable identity
- GDPR Art. 25 — Data protection by design: per-agent identity and scope reduction are the privacy-by-design mechanism for agent systems
On-Behalf-Of — Tying Agent Calls to User Consent
MCP and agent frameworks use OAuth under the hood. When your booking assistant calls the Calendar API "on your behalf" — how does the Calendar service know which user authorised this call, which agent is making it, and that the agent hasn't exceeded the scope you consented to? Without On-Behalf-Of, a plain bearer token answers none of these questions cryptographically.
The obo+jwt carries all three pieces the resource server needs:
"typ": "obo+jwt",
"sub": "user:alice@example.com", // human user
"act": {
"sub": "spiffe://example.com/agents/booking-assistant" // the agent
},
"scope": "read:calendar write:booking",
"iss": "https://as.example.com",
"aud": "https://booking-service.example.com",
"exp": 1720000300
}
| Standard | Role in OBO | Status | Implementation |
|---|---|---|---|
RFC 8693 — OAuth 2.0 Token Exchangegrant_type=urn:ietf:params:oauth:grant-type:token-exchange |
Base standard. Defines the subject_token + actor_token exchange and the act claim (§4.1) for identifying the acting party in a delegated token. |
● Published RFC | pkg/obo/ — OBOIssuer, OBOValidator, UserIssuer, UserValidator. 8 tests passing. |
| draft-gco-oauth-delegate-sd-jwt-00 Delegated SD-JWT for OAuth 2.0 |
Extension: replaces the plain JWT user token with an SD-JWT, enabling selective disclosure — the agent presents only the user attributes the target resource needs. Privacy-preserving OBO. | △ Individual draft (PoC implemented) | pkg/obo/sdjwt.go — SDUserIssuer, SDUserValidator, OBOIssuer SD path, 16 tests. Implemented to explore the privacy model; not WG-adopted. |
AgentToken (agent+jwt)The actor_token in the OBO request |
The agent's own identity token (SPIFFE URI subject, cnf.jwk binding) serves as the RFC 8693 actor_token. The OBO AS validates it before issuing the delegated token. |
● Implemented | pkg/identity/token.go + pkg/obo/issuer.go |
| Microsoft Entra OBO The widely-deployed precedent |
Microsoft's on_behalf_of grant is the production pattern this formalises — agents pass a user access token and receive a scoped downstream token. RFC 8693 standardises this across all OAuth 2.0 ASes. |
● Production (Entra ID) | Our demo reproduces this pattern with a self-signed IdP instead of Entra |
Plain OBO is powerful, but it exposes all user attributes (email, given_name, phone…) to the AS on every delegation. draft-gco-oauth-delegate-sd-jwt-00 extends OBO with Selective Disclosure JWTs: the agent presents only the claims the target resource actually needs. The AS can verify the user's identity without seeing attributes that aren't its business.
{
"sub": "user:alice@example.com",
"email": "alice@example.com", // AS sees this
"given_name": "Alice", // AS sees this too
"scope": "read:calendar"
}
{
"sub": "user:alice@example.com",
"_sd": ["hash(email)", "hash(given_name)"],
"scope": "read:calendar"
}~disclosure(email)~// given_name omitted
<JWT>~<disc1>~<disc2>~Each disclosure is
base64url(json([salt, claim_name, claim_value])). The JWT contains only SHA-256 digests in _sd — no raw PII. The agent selects which disclosures to include in the ~-separated presentation. The verifier rejects any disclosure whose digest isn't in _sd.
Implementation: pkg/obo/sdjwt.go — SDUserIssuer, SDUserValidator, SDUserToken.Present(). Status: proof-of-concept for the privacy model; draft-gco-oauth-delegate-sd-jwt-00 is an individual draft (not yet WG-adopted). This explores what privacy-preserving delegation could look like as the ecosystem evolves.
Proof of Possession — Binding Tokens to Keys
A bearer token is like cash: whoever holds it can spend it. Proof of Possession (PoP) changes this — the token is cryptographically bound to a key, and the caller must prove they hold that key on every request. Stolen tokens become useless. This project implements three complementary PoP mechanisms.
A bearer token is identity-agnostic — any holder gets access. Click to watch what happens when an attacker intercepts one.
cnf.jkt claim (JWK thumbprint). On every API call the agent sends a signed DPoP proof in a header — the resource server verifies the ephemeral key matches. No key, no access, even with a valid token.
└
pkg/cb4a/cdp.go — GenerateDPoPProof(), SimulateAPICall()
sub. A stolen token presented with a different cert is rejected — the attacker can't fake the cert without the private key.
└
internal/gateway/server.go — verifyMTLSBinding()
cnf.jwk key, covering the request URI and the full chain hash. Binds each API call to a specific agent workload and chain state. JTI replay protection prevents reuse.
└
pkg/identity/proof.go — GenerateProof(), ProofValidator
The CB4A CDP generates a fresh P-256 key pair per credential. The public key thumbprint (cnf.jkt) is embedded in the token. On every API call, the agent signs a DPoP proof with the private half:
{ "cnf": { "jkt": "SHA-256(pubkey)" } }
// DPoP proof header (sent with every request):
{ "jwk": <ephPublicKey>,
"alg": "ES256" }
// Resource server verifies:
SHA-256(proof.jwk) == token.cnf.jkt
proof.htm == request method
proof.htu == request URI
proof.ath == SHA-256(token)
jti not seen before (replay)
CDP.Mint() call generates a fresh ephemeral EC P-256 key. The private key lives only in memory for the duration of the credential. Compromise of one credential doesn't expose others.htm and htu.jti. The resource server records seen JTIs. A replayed proof (same JTI) is rejected even if the key and URI match — demonstrated in the Replay Attack tab.The gateway extracts the TLS client certificate URI SAN and checks it matches the AgentToken sub. A stolen token presented by an attacker with a different cert fails this check.
| Mechanism | Transport layer | Proof frequency | Key lifetime | Best for |
|---|---|---|---|---|
| DPoP (RFC 9449) | HTTP header (DPoP:) |
Every request | Per-credential (ephemeral) | API calls via CB4A-minted credentials; OAuth bearer replacement |
| mTLS (RFC 8705) | TLS handshake | Per connection | Cert validity period (e.g. 24h) | Service-to-service; gateway front-door; high-throughput agents |
| AgentProofToken (WPT) | HTTP header (Agent-Proof:) |
Every request | Minutes (short-lived JWT) | Agent-specific PoP; binds the proof to the full delegation chain hash |
cnf claim in the delegated token and require the agent to present a DPoP proof on every downstream call. This combination (RFC 8693 + RFC 9449) is the direction draft-gco-oauth-delegate-sd-jwt also points toward for privacy-preserving, possession-bound delegation.
| Standard | Role | Status | Implementation |
|---|---|---|---|
| RFC 9449 — OAuth 2.0 DPoP | Defines the DPoP proof header format, cnf.jkt token claim, and resource server verification steps |
● Published RFC | pkg/cb4a/cdp.go — GenerateDPoPProof, SimulateAPICall |
| RFC 8705 — OAuth 2.0 mTLS Client Auth and Certificate-Bound Access Tokens | Defines the token-cert binding model: resource server extracts the client cert and checks it against a token claim | ● Published RFC | internal/gateway/server.go — verifyMTLSBinding(), URI SAN == sub check |
| AgentProofToken / WPT — Workload Proof Token | Per-request proof JWT signed by the agent's workload key; covers chain hash + target URI; JTI replay detection | ● Implemented | pkg/identity/proof.go — GenerateProof, ProofValidator |
cnf.jwk claim so verifiers can later check proof-of-possession.
typ: agent+jwt · alg: ES256Header red
Payload purple
Header red
Payload purple
~ (same separator as SD-JWT disclosures). The Gateway validates every token in the chain in order, checking signatures, expiry, and that chain depths are strictly sequential (0, 1, 2…). Skipping or repeating a depth fails validation.
chain_depth=0 to appear as an orchestrator, gaining elevated privileges. The Gateway rejects any token whose depth is not exactly previous_depth + 1.
typ: application/agent-proof+jwtHeader red
Payload purple
cnf.jwk in the AgentToken). Its aud is the exact target URI, and chain_hash is SHA-256 of the full chain string. Even if an attacker captures this token, replaying it to a different endpoint fails the aud check — and replaying to the same endpoint fails the JTI replay detection.
- 1The Identity Provider generates an EC P-256 key pair — the root of trust for all agent tokens in this domain.
- 2The Orchestrator receives a signed agent+jwt with
role=orchestratorandchain_depth=0. Its public key is bound viacnf.jwk. - 3When delegating, the Executor also gets a agent+jwt with
chain_depth=1. The chain wire format is AT-1~AT-2. - 4The Gateway validates every token in the chain: signature, expiry, issuer, and strictly sequential chain depths.
- 5A per-request agent-proof+jwt binds the call to the exact target URI (
aud) and the chain hash, preventing reuse or redirection.
agent-proof+jwt with RFC 9421 HTTP Message Signatures — a transport-layer binding that covers the full HTTP request (method, path, headers, body digest) rather than just the URI. Tracked in standards-baseline.json as a future variant of pkg/identity/proof.go.
An attacker intercepts a valid Agent-Proof-Token and immediately replays it to gain unauthorized access. WIMSE prevents this with per-request JTI (JWT ID) replay detection maintained by the Gateway.
Request processed normally
agent-proof+jwt carries a unique jti (UUID). The Gateway's replay store records each seen JTI within the token's validity window. The second request with the same JTI is rejected before any business logic runs.
An attacker modifies the claims of a valid agent+jwt — for example, changing role from
executor to orchestrator to gain elevated privileges.
ES256 (EC P-256) makes this immediately detectable.
Header
Payload
base64url(header) + "." + base64url(payload). Any modification to either part — even a single byte — produces a completely different expected signature. The attacker cannot forge a valid signature without the IdP's private key.
jti; reuse is detected and rejected immediately.role, sub, or chain_depth) to escalate privileges.Authorizer interface (backed by OpenFGA). Every request is checked: subject × tool × action.chain_depth=0 to appear as an orchestrator, or skips depth levels to forge the delegation chain.aud claim is bound to the exact target URI. The Gateway validates that aud matches the request URI precisely.Agent-Chain-Token header with a chain where they have higher privileges, hoping the proof token will still be accepted.chain_hash = base64url(SHA-256(AT-1~…~AT-N)). The Gateway recomputes the hash of the presented chain — a substituted chain produces a different hash and the request is rejected.agent+jwt (e.g., from a log or compromised proxy) and replays it from a machine with a different TLS certificate.MTLSClientCA is set, verifyMTLSBinding() checks that the peer certificate's URI SAN equals the token's sub claim. The attacker's cert has a different URI SAN — the check fails. The agent uses the same EC key pair for mTLS cert, cnf.jwk, and proof signing, so compromising the token alone is useless without the private key.T9–T12 are attack vectors unique to autonomous AI agents. Traditional workload identity defences were not designed with reasoning processes, prompt injection, or runtime delegation in mind.
aud is bound to the exact target URI — a redirected call to an attacker-controlled URL won't produce a valid proof for that URL. CB4A Tier 1/2/3 governance limits what APIs can be called without explicit human approval. Full defence requires input sanitisation and prompt-level guardrails at the application layer — WIMSE addresses the credential binding, not the injection vector itself.sub and the entry-point agent's SPIFFE ID in act.sub (RFC 8693 §4.1 actor claim), both signed by the gateway's key. Every downstream service validates the Txn-Token independently — the attacker cannot forge the user context without the gateway's signing key. The WPT tth claim cryptographically binds every hop to the same originating transaction.sub; CB4A credentials are minted per-session with session-scoped vault entries; the gateway validates SPIFFE ID against expected tenant on every call — a cross-tenant tool call would present the wrong SPIFFE ID and be rejected. Full defence requires application-layer session isolation and LLM context sandboxing that WIMSE complements but does not replace.chain_depth is validated strictly sequentially (0, 1, 2…) — no gaps or resets. The OpenFGA Authorizer evaluates every request against the sub-agent's own SPIFFE ID, not the chain as a whole. The sub-agent's permitted actions are defined at AgentToken issuance time by the IdP — the orchestrator cannot grant permissions it doesn't have, and cannot expand scope through delegation. Each hop in the chain can only reduce, not extend, authority.- 🔑Identity — Every agent has an EC P-256 key pair. The public key is bound into the AgentToken via
cnf.jwk. - 🔗Delegation Chain — Multi-hop calls are represented as AT-1~AT-2~…~AT-N. Each hop's
chain_depthis validated sequentially. - 📝Proof of Possession — Each request carries a short-lived agent-proof+jwt signed by the caller's private key, bound to the target URI and chain hash.
- 🛡️Authorization — The Gateway enforces per-subject, per-tool, per-action authorization.
can_callimpliescan_read;can_writeimpliescan_read. - 🚫Replay Prevention — Every proof token has a unique
jti. The Gateway's replay store ensures each token can only be used once within its validity window.
Mutual TLS adds a transport-layer identity check on top of the application-layer token validation. Each agent presents an EC P-256 certificate with a SPIFFE URI SAN during the TLS handshake. The gateway verifies that cert.URI SAN[0] == AgentToken.sub — a stolen token is useless without the matching private key.
cnf.jwk in the AgentToken, and (3) the AgentProofToken signing key. Compromising the token alone is not enough — an attacker must also compromise the private key.
| Threat | Without mTLS | With mTLS |
|---|---|---|
| Stolen AgentToken | Thwarted by cnf.jwk proof (attacker needs private key) | Double protection: cert binding also checked |
| Network eavesdropping | Tokens visible in plaintext (HTTP) | TLS 1.3 encrypted channel (AEAD) |
| Identity spoofing | Prevented by IdP JWT signature | Cert URI SAN additionally bound to token sub |
| MITM / downgrade | Not prevented | TLS 1.3 min-version, no downgrade possible |
pkg/identity/proof.go.
Transaction Tokens
Each service in a multi-agent call chain sees an isolated request. Without a shared context token, there is no cryptographic way to link every hop back to the original user intent — making audit trails, authorization budgets, and per-transaction rate-limits impossible. Transaction Tokens (draft-ietf-oauth-transaction-tokens-11) solve this by issuing a signed JWT at the entry point that propagates unchanged through the entire chain.
The Txn-Token is issued once at the entry point and forwarded unchanged at every hop. Each agent attaches a fresh agent-proof+jwt with tth = SHA-256(txntoken), cryptographically binding the per-request proof to the originating transaction.
Click a button to start the flow.
sub=user, act.sub=agent SPIFFE ID (RFC 8693), txn IDtxntoken+jwt)// Header { "alg": "ES256", "typ": "txntoken+jwt" } // Payload { "iss": "https://tts.example", "sub": "alice@corp", // user identity "aud": ["https://api.example"], "txn": "a3f8-b12c-…", // unique txn ID "act": { // RFC 8693 §4.1 — acting agent "sub": "spiffe://mesh.example/agents/orch" }, "rctx": { "req_ip": "10.0.0.1", "req_wl": "spiffe://.../orch" }, "azd": [{ "type": "payment" }] }
tth binding// Header { "alg": "ES256", "typ": "application/agent-proof+jwt" } // Payload { "aud": "https://svc-b.example/api", "chain_hash": "SHA-256(chain)", "tth": "SHA-256(txntoken)", // ← binds to txn "jti": "unique-per-request", "exp": 1753649999 }
The act claim solves the AI agent forensics problem: when a Txn-Token propagates through a multi-hop chain,
downstream services need to verify not just who authorised the transaction (sub)
but which agent is actually executing (act.sub).
Without it, a rogue agent that forwards a stolen Txn-Token is indistinguishable from the legitimate orchestrator.
| Claim | Example value | Meaning |
|---|---|---|
sub |
alice@corp |
User who authorised the transaction — identical at every hop |
act.sub |
spiffe://mesh/agents/orch |
Agent executing this hop — signed by TTS, cryptographically unforgeable |
txn |
a3f8-b12c-… |
Transaction ID linking every hop into a single, correlated audit trail |
Without act | With act.sub |
|---|---|
| Svc B knows alice authorised something — but which agent made this specific call? | Svc B cryptographically proves spiffe://mesh/agents/orch called on alice's behalf |
| A rogue agent forwarding a stolen Txn-Token is indistinguishable from the orchestrator | Rogue agent's SPIFFE ID won't match the expected act.sub → rejected by policy |
| Audit log: "alice accessed /orders" — agent unknown | Audit log: "spiffe://orch acted as alice on txn a3f8" — full attribution |
act builds the full delegation traceact. The outermost act.sub is always the current actor — services apply access control only to it. Prior actors are preserved for audit (RFC 8693 §4.1).act.sub = spiffe://mesh/agents/executor
act.act.sub = spiffe://mesh/agents/orch
sub = alice@corp
act.sub. Prior actors are informational — do not grant them elevated permissions based on chain depth.
| Problem without Txn-Token | How Txn-Token resolves it | Tradeoffs |
|---|---|---|
| No cryptographic link between hop-level proofs and the original user request — audit logs show isolated events | txn ID is the same across all hops; every tth binding anchors the proof to the same JWT |
Cons:
Pros:
|
| Authorization context (scopes, budget, RAR details) known only at entry point — downstream services cannot verify intent | azd claim carries Rich Authorization Requests details from the AS; any service can inspect them |
|
| A compromised intermediate agent can forge its own proof and claim it's acting on behalf of the user | tth in AgentProofToken must match the TTS-issued Txn-Token — the intermediate cannot forge this binding without the Txn-Token private key |
|
| Call-chain scoping: an agent could reuse a proof in a different transaction context | The txn ID is unique per transaction; the receiving service checks both tth and jti, ensuring the proof is scoped to exactly one request in one transaction |
pkg/txntokenissuer := txntoken.NewIssuer( "https://tts.example", ttsKey, time.Minute*5, ) tok, _ := issuer.Issue(txntoken.IssueOptions{ Subject: "alice@corp", Audiences: []string{"https://api.example"}, ReqCtx: &txntoken.RequestContext{ ReqIP: "10.0.0.1", ReqWL: "spiffe://.../orch", }, })
tthproof, _ := identity.GenerateProof(
identity.ProofGenerateOptions{
TargetURI: "https://svc-b.example",
Chain: chain,
WorkloadKey: agentKey,
TxnToken: tok, // tth auto-set
},
)
// Validator: verify tth
pv.Validate(identity.ProofValidateOptions{
...,
TxnToken: tok,
})
10 new tests in pkg/txntoken/txntoken_test.go and 3 in pkg/identity/identity_test.go
cover happy path, typ header, txn ID propagation, authorization details round-trip, wrong key, expiry, issuer mismatch, hash determinism, and tth mismatch.
RFC 8693 — Application-to-Agent Task Delegation
When an application hands off a task to an AI agent, the agent must carry two provable identities into every downstream call: the user whose authority is being exercised (sub) and the application that authorised the delegation (act.sub). RFC 8693 Token Exchange is the standard mechanism — the app exchanges the user's OIDC token for a txntoken+jwt containing both, signed by a trusted Token Token Service (TTS). Without this, any agent holding a user token can act for that user without restriction.
Click Play to start the token delegation flow.
sub=user, act.sub=spiffe://.../support-apisub=user authorized ✓ & act.sub=app trusted ✓ → request approvedPOST /token HTTP/1.1 Host: tts.example Content-Type: application/x-www-form-urlencoded grant_type=urn:ietf:params:oauth:grant-type:token-exchange // user's identity — becomes sub in the output token subject_token=<OIDC JWT: sub=alice@corp> subject_token_type=urn:…:token-type:id_token // app's SPIFFE identity — becomes act.sub in the output token actor_token=<App SPIFFE JWT: sub=spiffe://.../support-api> actor_token_type=urn:…:token-type:jwt
// Header { "alg": "ES256", "typ": "txntoken+jwt" } // Payload { "iss": "https://tts.example", "sub": "alice@corp", // USER who authorised "act": { // RFC 8693 §4.1 "sub": "spiffe://.../support-api" // APP acting }, "aud": ["https://api.example"], "txn": "a3f8-b12c-…", "exp": 1753649999 }
| Claim | Value | What the Billing API enforces |
|---|---|---|
sub |
alice@customer.com |
Only process refunds for orders belonging to this customer |
act.sub |
spiffe://.../support-portal |
Only accept refund requests delegated by the official support portal — no other agent can initiate refunds, even with a valid user token |
act.sub ≠ spiffe://.../support-portal. The SPIFFE identity is signed by the TTS — it cannot be forged by the agent.
| Claim | Value | What the Finance system enforces |
|---|---|---|
sub |
bob@corp.internal |
Apply Bob's spending limits and department budget |
act.sub |
spiffe://internal/hr-portal |
Only the official HR portal may submit purchase orders — rogue agents or direct API calls are rejected |
act.sub provides this without additional instrumentation.
| Property | How RFC 8693 provides it | Why it matters for AI agents |
|---|---|---|
| Dual identity | sub (user) + act.sub (app) in one TTS-signed token |
Backend services enforce policy on both principals simultaneously — no second round-trip |
| Unforgeable binding | TTS signs the token — neither app nor agent can self-assert act.sub |
A compromised agent cannot elevate its own act.sub to impersonate a more trusted application |
| Least privilege | TTS restricts scope and aud independently of the original OIDC token |
Agent only receives the minimum audience and scope needed for its specific delegated task |
| Standard protocol | Published RFC — any OAuth 2.0-compliant library handles the exchange endpoint | No vendor lock-in; works with existing enterprise IdPs, API gateways, and SIEM tooling |
| Audit correlation | txn claim links every downstream service call to one originating request |
Compliance teams see: user → app → agent → service — from a single token, no scattered log joins |
SPIFFE Client Authentication
Agents already hold an AgentToken (agent+jwt) that proves their SPIFFE identity. Traditional OAuth client authentication requires pre-shared secrets — a rotation burden that breaks in ephemeral agent environments. SPIFFE Client Auth (draft-ietf-oauth-spiffe-client-auth-02) lets the agent use its existing token as a client assertion to authenticate to any OAuth 2.0 AS, eliminating secrets entirely.
No pre-shared client secret anywhere in the flow. The OAuth AS validates the AgentToken's signature against the IdP's public key — the same key it already trusts for workload identity.
Click Play to start.
POST /token)# application/x-www-form-urlencoded grant_type=client_credentials client_assertion_type=urn:ietf:params:oauth: client-assertion-type:jwt-bearer client_assertion=<compact AgentToken JWT> scope=read:tasks write:results # Response { "access_token": "<opaque 32-byte bearer>", "token_type": "Bearer", "expires_in": 3600, "sub": "spiffe://.../agents/orch" }
// pkg/spiffeclientauth auth := spiffeclientauth.NewAuthenticator( "https://as.example", identity.NewAgentValidator( "https://idp.example", idpPub, ), time.Hour, ) tok, err := auth.Authenticate( spiffeclientauth.AuthRequest{ ClientAssertion: agentToken, ClientAssertionType: "urn:…:jwt-bearer", Scope: "read:tasks", }, ) // tok.Sub == "spiffe://.../agents/orch"
| Problem without it | How SPIFFE Client Auth resolves it | Tradeoffs |
|---|---|---|
Agents need OAuth access tokens to call APIs — requires registering a client_id + secret that must be rotated |
Use the existing AgentToken as a client_assertion — no registration, no secrets, no rotation |
Cons:
Pros:
|
Secret sprawl — each agent deployment needs a unique client_secret, stored in environment or vault |
The SPIFFE identity of the agent is the credential — no additional secret material needed | |
| Minted tokens carry no agent identity — API can't tell which agent made the call | sub claim in the access token response carries the SPIFFE ID, enabling per-agent authorization and audit |
pkg/spiffeclientauth
8 tests in pkg/spiffeclientauth/spiffeclientauth_test.go cover happy path, token uniqueness per call,
wrong assertion type, empty assertion, tampered AgentToken, expired token, issuer mismatch, and empty scope passthrough.
pkg/identity and internal/gateway packages are unchanged.
pkg/spiffeclientauth is an independent add-on that any OAuth AS endpoint can integrate.
Identity Chaining
An agent in Cloud A holds a SPIFFE identity token issued by Cloud A's IdP. When it needs to call a service in Cloud B, Cloud B's AS won't accept Cloud A's token — different trust domains, different signing keys. Identity Chaining (draft-ietf-oauth-identity-chaining-17) bridges this gap with a JWT Authorization Grant that cross-domain services can validate without pre-configuring shared secrets between the two AS instances.
Cloud B only needs Cloud A's public signing key — no pre-shared secret between the two AS instances. The agent's SPIFFE ID flows through the grant's sub claim, giving Cloud B full auditability of cross-domain calls.
jwt-authz-grant)// Header { "alg": "ES256", "typ": "jwt-authz-grant" } // Payload { "iss": "https://as.cloud-a.example", "sub": "spiffe://cloud-a.example/agents/orch", // propagated identity "aud": ["https://as.cloud-b.example/token"], "act": { // RFC 8693 §4.1 — calling agent "sub": "spiffe://cloud-a.example/agents/sub-agent" }, "iat": 1753649000, "exp": 1753649300, // 5 min TTL "jti": "kY2mNpXx…" // replay prevention }
pkg/identitychaining// Domain A: issue grant gi := identitychaining.NewGrantIssuer( "https://as.cloud-a.example", domainAKey, agentValidator, 5*time.Minute, ) grant, _ := gi.Issue( agentToken, "https://as.cloud-b.example/token", // optional RFC 8693 §4.1 actor — set when delegating agent differs from sub &identitychaining.ActorClaims{Sub: "spiffe://cloud-a.example/agents/sub-agent"}, ) // Domain B: validate grant gv := identitychaining.NewGrantValidator( "https://as.cloud-a.example", domainAPub, ) claims, _ := gv.Validate( grant, "https://as.cloud-b.example/token", ) // claims.Subject == "spiffe://cloud-a.../agents/orch"
When an orchestrator delegates a cross-domain call to a sub-agent, the JWT Authorization Grant
carries both identities: the original SPIFFE ID in sub (the propagated identity that Cloud B
authorises against) and the actual calling agent in act.sub. Cloud B can then answer two
distinct questions: who is this agent claiming to represent? and which agent is actually
making the call right now?
| Grant claim | In this scenario | Cloud B can verify |
|---|---|---|
sub |
spiffe://cloud-a/agents/orch |
Orchestrator's identity — Cloud B applies authz policy to this SPIFFE ID |
act.sub |
spiffe://cloud-a/agents/sub-agent |
Sub-agent physically making the call — different from the authorising identity |
iss |
https://as.cloud-a.example |
Both sub and act.sub attested by Cloud A's AS — no direct trust needed |
act omitted){
"sub": "spiffe://cloud-a/orch",
"aud": ["https://as.cloud-b/token"]
// sub IS the caller — single-agent
}
act present){
"sub": "spiffe://cloud-a/orch",
"act": {
"sub": "spiffe://cloud-a/sub-agent"
}, // RFC 8693 §4.1
"aud": ["https://as.cloud-b/token"]
// act.sub IS the caller; sub authorised it
}
act.sub, Cloud B's audit log shows only the orchestrator's SPIFFE ID — it cannot tell
which sub-agent executed the action. With act.sub, the full delegation chain is cryptographically
attested: Cloud A's AS signed both identities, so Cloud B can log
"sub-agent X acted on behalf of orchestrator Y, both from Cloud A" with no additional infrastructure.
| Problem without it | How Identity Chaining resolves it | Tradeoffs |
|---|---|---|
| Agent in cloud-a.example cannot authenticate to cloud-b.example APIs — their IdPs have different signing keys and different trust domains | Domain A's AS issues a jwt-authz-grant that is addressed specifically to domain B's token endpoint — domain B validates it with only domain A's public key |
Cons:
Pros:
|
| Token exchange via RFC 8693 requires domain A and B to share infrastructure or a common token exchange service | The JWT grant is self-contained and cryptographically verifiable — domain B needs no connection to domain A beyond the initial public key exchange | |
| Cross-domain calls lose the agent's SPIFFE identity — cloud B sees an opaque service token | sub in the grant carries the original SPIFFE ID; cloud B can use it for per-agent authorization and audit |
pkg/identitychaining
10 tests in pkg/identitychaining/identitychaining_test.go cover the happy path,
typ header verification, audience mismatch, wrong signing key, expired grant, issuer mismatch,
invalid AgentToken, missing subject/audience inputs, and a full end-to-end cross-domain flow.
pkg/identity, pkg/spiffeclientauth, and
internal/gateway are unchanged. pkg/identitychaining is an independent package
that layers on top of the existing AgentValidator.
pkg/identitychaining and pkg/identity/chain.go.
pkg/identitychaining carries today: in a future revision, the grant token could embed a propagation-context claim linking every hop back to the originating user intent.
Implementing draft-hartman-credential-broker-4-agents-00 (March 2026) in Go — compiled to WASM. All JWT signing, PDP evaluation, CDP minting, DPoP binding and verification run live in your browser.
CB4A solves AI agent credential sprawl: AI agents increasingly need to call external APIs (Stripe, GitHub, AWS, databases). Injecting long-lived API keys into agents creates massive exposure. CB4A introduces a Policy Decision Point (PDP) that evaluates every credential request, and a Credential Delivery Point (CDP) that mints short-lived, DPoP-bound tokens from a vault. Agents never hold real credentials.
AgentToken (
agent+jwt) + SPIFFE SVID prove agent identity to the gateway. Delegation chains show the full call path. mTLS binds cert to token.
The agent presents its SPIFFE SVID in the Task Request Envelope. CB4A's PDP evaluates the request; the CDP mints a short-lived DPoP-bound token from the vault. The base secret never leaves the vault.
cnf.jkt. Per-request DPoP proof with htm, htu, ath. Stolen tokens are useless.tre+jwt)Authorization: DPoP <token> + DPoP: <proof>analytics:events:readbilling:invoices:writeClick Initialize to boot the CB4A system in your browser. Ephemeral PDP signing keys are generated, a CDP is wired to an in-memory vault pre-seeded with 5 credential scopes, and an immutable audit log is created. No network calls — all Go code runs via WASM.
AI agents need credentials to call external APIs. How you manage those credentials determines your blast radius when an agent is compromised. Here's how CB4A compares — and where it complements tools you already have.
| Approach | Agents hold real creds? | Per-request policy? | Human approval? | Token binding? | Immutable audit? |
|---|---|---|---|---|---|
| Static Secrets env vars / baked into containers |
Always — in memory, logs, cores | None | None | None | None |
| HashiCorp Vault agent retrieves secret via API |
⚠ Briefly (after retrieval) | None post-retrieval | None | None | ⚠ Vault access log only |
| 1Password / AWS Secrets Mgr cloud-native secret stores |
⚠ Briefly (after retrieval) | None post-retrieval | None | None | ⚠ Access log only |
| CB4A brokered credential issuance |
Never — DPoP token only | Every request — PDP evaluates | Tiered: auto / HITL / FIDO2 MFA | DPoP RFC 9449 (cnf.jkt) | Append-only, fail-closed |
Deployment Scenarios
Migrating from Static Secrets
API keys baked into Docker images or injected as env vars are the worst credential pattern. Any agent compromise, log leak, or process dump exposes the key with no TTL and no scope constraint.
CB4A replaces key injection entirely. Agents authenticate with their SPIFFE SVID and receive short-lived, scoped, DPoP-bound tokens per request. The API key never leaves the vault. A compromised agent can only use its current token until expiry — and only for the approved scope and URI.
HashiCorp Vault Already Deployed
Your team already uses Vault for secret storage — dynamic secrets, PKI, KV v2. Agents currently call vault read secret/api-key and use the returned value directly. The problem: after retrieval, the agent holds a long-lived credential Vault can't revoke without a full rotation.
CB4A adds the governance layer Vault lacks. The CDP is the only entity in Vault's access policy for the credential paths. Agents call CB4A, not Vault. CB4A adds: per-request PDP evaluation, DPoP sender-constraint (stolen token useless), tiered human approval for sensitive scopes, and an immutable audit trail correlating request → decision → API usage.
vault policy: agent-read with cdp-only-read. No secret migration needed — Vault stays as the HSM backend.AWS Secrets Manager / 1Password for Business
Cloud-native secret stores offer rotation, cross-region replication, IAM policies, and audit logs. Agents use SDK calls to retrieve credentials at startup or per-request. But retrieval still hands the raw credential to the agent process — post-retrieval exposure is identical to static secrets.
CB4A uses your cloud SM as the vault backend. The CDP reads from AWS Secrets Manager or 1Password's API. Your existing secrets, rotation schedules, and IAM policies remain unchanged. What changes: agents never call AWS SM directly; they call CB4A. The cloud SM IAM policy restricts direct access to the CDP service account only. CB4A adds per-request approval, DPoP binding, and cross-agent correlation that cloud SM audit logs cannot provide.
Status: monitoring. Tracked in standards-baseline.json as draft-sweeney-wimse-credential-delegation.
CB4A's Policy Decision Point (PDP) is a central component — it decides whether an AI agent may obtain a credential for a given scope and API. Two new IETF OAuth WG drafts (rev 00, early stage) directly formalise this pattern: draft-gazitt-oauth-authzen-token-exchange and draft-gazitt-oauth-authzen-issuance.
InMemoryPDP already implements the logical equivalent: it receives a subject (SPIFFE ID), resource (scope + API path), action, and context (tier/budget), and returns an approved/denied decision with a signed JWT.
The AuthZEN five-tuple maps exactly onto these inputs — our PDP is a simplified AuthZEN-shaped component.
standards-baseline.json as draft-gazitt-oauth-authzen-token-exchange and draft-gazitt-oauth-authzen-issuance. No implementation changes yet — monitoring for WG adoption.
Integrating the x402 HTTP payment protocol with AI Agent Security (AgentToken + CB4A). Instead of EVM wallet signatures, agents present CB4A-DPoP credentials as payment authorization — fully cryptographically bound to the paying agent's SPIFFE identity.
AI agents increasingly need to call pay-per-use APIs (data, compute, external tools). x402 standardises HTTP-level payment — but who is authorising the spend? WIMSE + CB4A answer this: the PDP evaluates spending authority, the CDP mints a short-lived DPoP-bound payment credential, and the WIMSE AgentToken cryptographically proves which agent authorised each payment.
402 Payment Required + PaymentRequired JSON. Agent retries with X-Payment header. No blockchain required in this implementation.payment:ASSET:AMOUNT. No raw secrets ever leave the vault.agent+jwt) is included in every payment payload. Gateway verifies that the token's sub matches the CB4A agent_svid. Cryptographic proof of payer identity — no impersonation.All cryptography runs in your browser via WASM. Real EC P-256 keys, real JWTs, real DPoP proofs.
| Time | Event | Agent | Scope | Tier | Status |
|---|---|---|---|---|---|
| No events yet | |||||
jti. The PaymentGateway tracks consumed proofs — replaying the same payment payload is detected and rejected immediately.sub must match CB4A agent_svid. An agent cannot present another agent's CB4A credential — identity and spending authority are cryptographically linked.payment:AGENT_CREDIT:50). The PDP enforces spending tiers. Agents cannot self-escalate their payment limits.CIBA — Human-in-the-Loop Approval Gates
AI agents acting autonomously on high-risk tasks — financial transactions, code deployments, data deletions — must sometimes pause and wait for a human decision before proceeding. Client-Initiated Backchannel Authentication (CIBA) is the OAuth 2.0 mechanism for this: the agent initiates an auth request to a backend channel and polls until a human approves or denies on their device. Defined in draft-klrc-aiagent-auth §6 and OpenID CIBA Core.
backchannel_authentication_request with login_hint (SPIFFE URI) and scope. Polls /token with auth_req_id until approved or denied.access_token bound to the specific approved action.backchannel_authentication_request with login_hint=spiffe://mesh/workeraccess_token bound to this auth_req_id (single-use, short-lived)auth_req_id, preventing replay across different requests.
agent_mission claim (e.g. "Summarise Q2 financial reports"), the CIBA push notification can surface it to the human: "Agent 'Summarise Q2 reports' wants to transfer $2,400 — does this match the approved mission?" This gives the approver context to detect scope creep before approving.
CB4A Tier 2 (HITL) requires human approval. WebAuthn Level 4 gives that approval a cryptographic binding: the approver proves physical presence via a FIDO2/passkey assertion (EC P-256, ES256). The PDP verifies the signature before issuing the Decision JWT — no password, no session cookie, no replay possible.
SSF / CAEP — Compromised Agent Detection & Revocation
A valid AgentToken is accepted until it expires — even if the agent has been compromised. Shared Signals Framework (SSF) closes this gap: a SIEM or IdP acting as an SSF Transmitter monitors agent behaviour continuously and emits a signed Security Event Token (SET) the moment it detects an anomaly. The gateway (SSF Receiver) immediately revokes the agent's access — before the token expires. Defined in draft-klrc-aiagent-auth §7 and draft-ietf-sharedsignals-framework.
session-revoked — immediate terminationcredential-change — key compromise suspectedassurance-level-change — integrity degradedtoken-claims-change — claims no longer trusted
sub (SPIFFE URI), adds to revocation store. All subsequent requests from that agent return 401 Unauthorized immediately.session-revoked SET (ES256) to registered gateway receiverspiffe://mesh/worker to revocation store401 Unauthorized — token rejected despite being validsub is the agent's SPIFFE URI — the same value in every AgentToken's sub claim and every AgentChain hop. The revocation check is a single set lookup in internal/gateway/server.go: if revokedSubs.Contains(claims.Subject) → 401. No per-token tracking needed.
In-browser WebAssembly only — no server. Steps execute sequentially with real cryptography.
typ: agent+jwt for spiffe://agent-fabric.example/compromised-workerIsRevoked(sub) — agent is active, gateway would accept tokensession-revoked SET, signs with ES256POST /ssf/events (Content-Type: application/secevent+jwt) → IsRevoked(sub) again — agent is now blocked, 401 on next requestAgent Memory Security
AI agents accumulate persistent memory — saved preferences, conversation history, RAG context, and episodic summaries — often backed by a vector database (pgvector, Weaviate, Chroma, Qdrant, and others). This memory is a high-value attack surface: poison it, and every future decision the agent makes is compromised. MITRE ATLAS AML.M0031 (Memory Hardening) and OWASP ASI06 define the controls needed to protect it.
Every write is validated and hashed. Every read verifies the hash. Vector DB row isolation enforces per-agent namespace separation.
Try writing different content to the memory store. Write-time detectors run on every entry before persistence.
Agent A writes sensitive data to its memory namespace. Agent B (different SPIFFE sub) attempts to read it.
An attacker modifies a memory entry directly in the database, bypassing the store API. The integrity check catches it on the next read.
| AML.M0031 Control | OWASP ASI06 | Implementation |
|---|---|---|
| Created — validate at write time | Prompt injection detector, size anomaly | validateContent() in pkg/memory |
| Modified — detect post-write changes | Key tampering detector | ComputeIntegrity() SHA-256 on read |
| Isolated — per-agent namespace | Cross-agent retrieval prevention | AgentSub key + vector DB row isolation policy |
| Audited — immutable op log | Forensic record of all writes | AuditRecord per AgentSub |
| Recovered — rollback on integrity failure | Snapshot & rollback | Roadmap: snapshot before write, restore on IntegrityError |
-- PostgreSQL + pgvector: enforce per-agent isolation at the DB layer
ALTER TABLE agent_memories ENABLE ROW LEVEL SECURITY;
CREATE POLICY agent_isolation ON agent_memories
USING (agent_sub = current_setting('app.agent_sub'));
-- Gateway middleware sets session variable before every query:
-- SET LOCAL app.agent_sub = '<AgentToken.sub>'
-- Equivalent patterns exist for Weaviate (tenants), Qdrant (payload filtering), Chroma (metadata filters)
OAuth 2.0 and OpenID Connect are the foundation. The standards below address the specific gaps that emerge when the client is an autonomous AI agent. Four approaches: XAA (Cross App Access), AAuth (Agent Authorization), CB4A (Credential Broker for Agents), and WIMSE (Workload Identity in Multi-System Environments).
These are complementary, not competing — each fills a different gap that OAuth + RFC 8693 alone cannot address. An enterprise agent deployment may use all four simultaneously.
JWT chains, mTLS, OpenFGA authz. The internal service mesh identity layer.
PDP/CDP split, vault-backed, tiered approval. Credential brokering layer.
HTTPSig, agent_token, per-call intent. Replaces API keys with signatures.
OAuth extension, ID-JAG token. Eliminates consent prompts for enterprise agent flows.
OAuth 2.0, OpenID Connect, and RFC 8693 Token Exchange are the bedrock.
They solve user authentication, consent, delegation (sub + act.sub), and scoped access.
The newer protocols — WIMSE, XAA, AAuth, CB4A — are not replacements.
They fill the specific gaps that emerge when the client is an autonomous AI agent rather than a human-operated browser or mobile app.
| Capability | OAuth 2.0 + OIDC + RFC 8693 | The Gap — AI agents expose this | Filled by |
|---|---|---|---|
| User authentication | Auth Code + PKCE, OIDC ID Token | — | Fully covered |
| Delegation identity | RFC 8693 sub + act.sub — who delegated to whom |
— | Fully covered |
| Scoped access | OAuth scopes, Rich Auth Requests (RFC 9396) | Scopes are static at request time. An agent assembles tool chains at runtime — intent can't be pre-declared. | AAuth: per-call intent embedded in every signed request |
| Agent cryptographic identity | No. The agent holds a Bearer token it received — it can't prove it owns a private key. | A stolen Bearer token gives full access. No way to bind a token to the agent that should hold it. | WIMSE cnf.jwk / DPoP (RFC 9449) / AAuth HTTPSig |
| Call chain proof | ⚠ RFC 8693 records who delegated, but not the cryptographic call path | Agents invoke agents invoke agents — the delegation tree can be spoofed without signed chain proofs. | WIMSE AgentChain (agent+jwt chaining), Txn-Token |
| Mid-task authorization states | No. Authorization failure is an error — caller must retry from scratch. | An autonomous agent mid-task cannot interrupt to show a consent UI. It needs native pending/approved/denied states. | AAuth: pending is a first-class state, not an error |
| External API credential isolation | No. Token Exchange gives the agent a Bearer token it holds long-term. | If the agent process is compromised, all external secrets (Stripe, AWS, Salesforce) are exposed. | CB4A: vault-backed, DPoP-bound, per-request governed |
| Enterprise consent-free delegation | ⚠ RFC 8693 exchange requires per-user consent or pre-agreed trust — no enterprise-wide policy model | Autonomous agents trigger consent prompts every time they call an OAuth-protected SaaS API — unusable at scale. | XAA ID-JAG: enterprise admin pre-authorizes, zero per-call prompts |
XAA extends OAuth to let an enterprise Identity Provider manage app-to-app connections without per-user consent prompts. It implements the Identity Assertion Authorization Grant (ID-JAG) — pre-authorized by enterprise policy, no user interaction after initial setup.
ID-JAG delegation token — pre-authorized by enterprise admin, no user consent prompt
AAuth is an HTTP authorization protocol where every agent gets its own cryptographic identity via HTTPSig and a signed
agent_token. Created by Dick Hardt (OAuth 2.0 original author). Coexists with OAuth 2.0 / OIDC rather than replacing them.
agent_token (cryptographic identity). Requests are signed with HTTPSig. Resources issue resource_token describing requirements; person servers issue auth_token granting access. Pending is a first-class state — not an error.agent_token. Resource verifies directly. No intermediary required.auth_token. Mid-task consent with pending states natively supported.agent_token. Request carries per-call intent — not just a static scope.resource_token describing requirements. Agent receives a pending state (not an error).auth_token. Agent retries with auth_token — access granted by Protected API.draft-hardt-oauth-aauth-protocol-10 (Aug 2026). Reference implementation at aauth.dev. Not yet adopted as a WG work item; protocol is stabilizing across four access modes.| Dimension | WIMSE | CB4A | AAuth | XAA |
|---|---|---|---|---|
| Primary problem | Agent-to-agent identity chains | External API credential sprawl | Agent lacks cryptographic identity | OAuth consent friction for agents |
| Agent identity | AgentToken JWT (ES256, cnf.jwk) |
SPIFFE SVID via SPIRE | agent_token + HTTPSig signing |
Inherited from user OAuth session |
| Token type(s) | agent+jwt, agent-proof+jwt |
tre+jwt, pdp-decision+jwt, cb4a-token+jwt |
agent_token, resource_token, auth_token |
ID-JAG delegation + standard Bearer |
| Token binding | cnf.jwk (mandatory) |
DPoP RFC 9449 (cnf.jkt) |
HTTPSig per-request (per-call) | None — standard Bearer token |
| External API access | → CB4A handles this | Core use case — vault-backed | Yes — any HTTP resource | Yes — OAuth-protected only |
| Human approval | None | Tier 1/2/3 (auto / HITL / MFA) | First-class pending / consent state | ⚠ Initial setup only |
| Mid-task auth states | None | ⚠ Via HITL approval queue | Native pending state support | None |
| Cross-domain | ⚠ Via OID-FED exchange | ⚠ Trust domain scoped | Mode 4 federated | Enterprise IdP federation |
| Credential isolation | N/A — no external creds | Vault-backed, agents hold tokens not secrets | No shared secrets — signatures only | Bearer token held by agent |
| Infrastructure cost | Low — IdP + Gateway | High — SPIRE + PDP + CDP + Vault | Medium — identity + person servers | Low — existing enterprise IdP |
| Standards status | IETF WIMSE WG (active) | Individual draft, March 2026 | Draft by Dick Hardt, in dev | xaa.dev · RFC 8693 / RFC 7523 |
These four standards are not alternatives — they solve different problems at different layers. A mature agent deployment uses whichever combination its architecture requires.
agent+jwt) + AgentProofTokens. JWT delegation chains prove the full call path. mTLS cert-token binding. SPIFFE SVID as the universal identifier across all layers.agent_token + HTTPSig, with native support for mid-task consent states (pending/approved/denied). Complements WIMSE for HTTP-native agent environments.This PoC implements fourteen IETF and OpenID standards (plus two new patterns from draft-klrc-aiagent-auth) — most of them active drafts still evolving toward production readiness. A GitHub Actions workflow checks the IETF Datatracker API daily and opens a labelled issue when any revision changes. The Impl. commit column links to the exact commit that last implemented or verified compatibility with that draft revision.
Last checked: 2026-08-07 ·
standards-baseline.json ·
standards-tracker.yml
| Standard | Working Group | IETF Status | PoC Status | Rev | Files | PoC impact | Impl. commit |
|---|---|---|---|---|---|---|---|
| WIT — Workload Identity Credentials | IETF WIMSE WG | ● WG Draft | ✓ Implemented | -02 |
pkg/identity/token.go, pkg/identity/chain.go |
AgentToken (agent+jwt) extends WIT — any cnf.jwk or typ changes affect pkg/identity |
12a6936 |
| WPT — Workload Proof Token | IETF WIMSE WG | ● WG Draft | ✓ Implemented | -01 |
pkg/identity/proof.go, internal/gateway |
AgentProofToken (application/agent-proof+jwt) — aud, wth, tth=SHA-256(Txn-Token), jti |
ed3bd65 |
| Txn-Token — OAuth 2.0 Transaction Tokens | IETF OAuth WG | ● WG Draft | ✓ Implemented | -11 |
pkg/txntoken, pkg/identity (tth) |
typ=txntoken+jwt, propagates user identity + authz context through agent chains; tth binds AgentProofToken to the originating transaction |
ed3bd65 |
| Token Exchange — OAuth 2.0 Token Exchange | IETF OAuth WG | ● Published RFC | ✓ Implemented | RFC 8693 |
pkg/obo/, pkg/txntoken, pkg/identitychaining |
act.sub (actor claim §4.1) — OBO delegated token carries both user sub and agent act.sub; also used in Txn-Token and JWT Authorization Grant for multi-hop chains |
b634b10 |
| Delegate SD-JWT — Delegated SD-JWT for OAuth 2.0 | IETF OAuth WG | ◌ Individual draft | △ PoC implemented | -00 |
pkg/obo/sdjwt.go — SDUserIssuer, SDUserValidator, OBOIssuer SD path (16 tests) |
Extends RFC 8693 OBO with SD-JWT selective disclosure — agent presents only the claims the target resource needs. PoC implemented to explore the privacy model; not WG-adopted. Demonstrated in the On-Behalf-Of tab. | current |
| Identifiers — Workload Identifier | IETF WIMSE WG | ● WG Draft | ✓ Implemented | -03 |
pkg/identity/token.go, pkg/keys/mtls.go |
SPIFFE URI format for agent SVIDs — spiffe://trust-domain/path |
12a6936 |
| mTLS — Mutual TLS Binding | IETF WIMSE WG | ● WG Draft | ✓ Implemented | -02 |
pkg/keys/mtls.go, internal/gateway/server.go |
Token-cert binding in gateway middleware — URI SAN matching against AgentToken sub |
73f519b |
| Arch — WIMSE Architecture | IETF WIMSE WG | ● WG Draft | ✓ Implemented | -08 |
pkg/identity/chain.go, internal/gateway |
Token exchange, trust domain boundaries, delegation chain semantics | 018b9d8 |
| AI Agent Auth & Authz draft-klrc-aiagent-auth | Individual (Kasselman, Parecki, Steele…) | ● Individual draft | ◌ Monitoring | -03 |
pkg/identity/token.go (agent_mission), demo CIBA + SSF tabs |
Validates WIMSE WPT/WIT design; introduces agent_mission claim, CIBA human-in-the-loop approval gates, SSF/CAEP agent revocation signals. Missed by wg_feeds (individual submission — no ietf- prefix); keyword-search feed added to tracker. |
— |
| CB4A — Credential Broker for Agents | Individual (S. Hartman) | ● Individual draft | ✓ Implemented | -00 |
pkg/cb4a |
TRE JWT, PDP decision JWT, CB4A token, Tier model, DPoP binding via cnf.jkt |
77d0232 |
| DPoP — OAuth 2.0 Demonstrating Proof of Possession | IETF OAuth WG | ● Published RFC | ✓ Implemented | RFC 9449 |
pkg/cb4a/cdp.go |
DPoP proof JWT (dpop+jwt), cnf.jkt thumbprint, ath binding, jti replay protection in CDP |
77d0232 |
| OID-FED — OpenID Federation 1.0 | OpenID Foundation | ● Published (OIDF) | ✓ Implemented | 1.0-41 |
pkg/federation, internal/gateway/multivalidator.go |
Entity Configuration, Subordinate Statement, authority_hints, chain resolution for cross-org AgentToken validation |
dd5b164 |
| SPIFFE Client Auth — JWT-bearer OAuth assertion | IETF OAuth WG | ● WG Draft | ✓ Implemented | -02 |
pkg/spiffeclientauth |
AgentToken as client_assertion (urn:…:jwt-bearer) — eliminates pre-shared OAuth client secrets for agent workloads |
018b9d8 |
| Identity Chaining — JWT Authorization Grant | IETF OAuth WG | ● WG Draft | ✓ Implemented | -17 |
pkg/identitychaining |
typ=jwt-authz-grant, cross-trust-domain propagation of agent SPIFFE IDs; aud=target AS endpoint, jti replay protection |
018b9d8 |
| JWT-BCP — JWT Best Current Practices | IETF OAuth WG | ● Active BCP | ✓ Implemented | bis |
All validator files | Compliance audit 2026-08-02: all parsers now enforce WithValidMethods(ES256), WithExpirationRequired, WithIssuedAt, and typ header checks — mitigates algorithm confusion, alg:none, and missing-exp attacks |
9d73ac4 |
| HTTP-Sig — WIMSE HTTP Message Signature Auth | IETF WIMSE WG | ● WG Draft | ◌ Monitoring | -06 |
pkg/identity/proof.go (future) |
Alternative to AgentProofToken using RFC 9421 HTTP Message Signatures — tracked as potential replacement for JWT-based agent proof if WG adopts as primary mechanism. Bumped -05 → -06 on 2026-08-04. | monitoring |
| WI-Practices — WIMSE Workload Identity Practices | IETF WIMSE WG | ● WG Draft | ◌ Monitoring | -06 |
pkg/cb4a, pkg/identity |
Best practices for workload credential management: short-lived creds, no long-lived agent secrets, cert rotation. Validates AgentToken short-TTL design and CB4A vault-backed no-secret-at-rest model. Note: draft is intentionally independent of WIMSE architecture specs — complements rather than duplicates them. Primary coverage in wimse-identity-fabric. | monitoring |
| AuthZEN-TE — AuthZEN Binding for OAuth 2.0 Token Exchange | IETF OAuth WG | ◌ Individual draft | ◌ Monitoring | -00 |
pkg/txntoken, pkg/identitychaining, internal/authz |
Externalises the AS policy decision inside RFC 8693 Token Exchange (identity chaining, Txn-Token issuance) to an AuthZEN PDP API. Directly covers agent-to-agent token exchange flows. Our InMemoryPDP in internal/authz is a simplified stand-in for the AuthZEN five-tuple evaluation model — if WG adopts, implementation connects CB4A PDP to a standard AuthZEN API. |
monitoring |
| AuthZEN-Issuance — AuthZEN Profile for OAuth 2.0 Token Issuance | IETF OAuth WG | ◌ Individual draft | ◌ Monitoring | -00 |
internal/authz |
Parent spec: defines the AuthZEN five-tuple (subject, resource, action, context, environment) for externalising any OAuth token issuance decision to a PDP. Companion to AuthZEN-TE. The five-tuple model mirrors the decision inputs our InMemoryPDP evaluates per agent request. |
monitoring |
| CredDelegation — Credential Delegation Protocol for AI Agents | IETF WIMSE WG | ● Individual draft | ◌ Monitoring | — |
pkg/cb4a (future) |
Competing/complementary approach to CB4A for AI agent credential delegation — introduces typed delegation tokens; noted in Credential Broker tab | monitoring |
| CrossOrgDelegation — Cross-Org Workload Delegation | IETF WIMSE WG | ● Individual draft | ◌ Monitoring | — |
pkg/identitychaining, pkg/identity/chain.go |
Formalises cross-org delegation requirements — informs AgentChain semantics and Identity Chaining scenario | monitoring |
| PropagationContext — Multi-Hop Identity Context | IETF OAuth WG | ● Individual draft | ◌ Monitoring | — |
pkg/identitychaining, pkg/txntoken |
Multi-hop identity propagation context travelling with delegation chains — future extension of Identity Chaining and Txn-Token scenarios | monitoring |
scripts/check_standards.py each morning. The script queries the IETF Datatracker REST API for each tracked draft and compares the returned rev field against standards-baseline.json.last_known_rev is updated and committed with [skip ci]. A developer manually sets implemented_rev in the baseline file after updating the Go code to match the new revision.standards-update labelled issue lists the new revision, PoC impact, affected files, and a checklist: read the IETF diff, check for breaking typ / claim / header changes, update the Go implementation, bump implemented_rev.Each standards-update issue must be triaged, reviewed for breaking changes, and verified against the threat model before it can be closed. See docs/standards-tracking.md — checklist.
alg:none, missing exp/iat, typ header confusion, replay, audience escalation, key confusion, and DPoP replay (where applicable).jwt.NewParser calls across the PoC now enforce WithValidMethods(["ES256"]), WithExpirationRequired(), and WithIssuedAt(). Algorithm confusion, alg:none, and far-future token attacks are fully mitigated.