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 WIMSE 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
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.azp (authorising party) claim, 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.
An agent from Org B calls a gateway controlled by Org A. The gateway has no static entry for Org B's IdP — it resolves the key dynamically via an OpenID Federation trust chain: Trust Anchor → Subordinate Statement → Entity Configuration.
Header red
Payload purple
typ: entity-statement+jwt). It carries Org B's public key and an authority_hints list — the pointers that tell a resolver where to find the Trust Anchor that can certify this entity.
Header
Payload
agent+jwt type and ES256. The only difference is the iss claim points to Org B's IdP rather than Org A's. The Gateway has no static config for this issuer — it must resolve it dynamically.
iss from token — not in static validator mapidp.org-b.example/.well-known/openid-federationauthority_hints → Trust Anchor: …x=…- 1The Trust Anchor generates a key pair and signs a Subordinate Statement (SS) certifying Org B's IdP public key.
- 2Org B's IdP publishes a self-signed Entity Configuration (EC) JWT at
/.well-known/openid-federationwithauthority_hintspointing to the anchor. - 3Org B's agent is issued a standard agent+jwt — identical in format to a local agent token.
- 4When the token arrives at Org A's Gateway, it peeks at
iss, finds no static validator, and falls back to the Federation Resolver. - 5The resolver walks the chain: EC → authority_hints → SS → anchor key → SS signature verified → leaf key extracted → EC signature verified.
- 6The token is validated with the dynamically-resolved key. No pre-shared keys or static config were needed for Org B.
Agent Fabric tokens (agent+jwt), proof tokens (application/agent-proof+jwt), and OID-FED entity statements all use ES256 (EC P-256). Shor's algorithm on a quantum computer breaks EC in polynomial time — every delegation chain, every proof token, every trust chain becomes forgeable.
EC P-256 relies on the Elliptic Curve Discrete Logarithm Problem: given public point Q = k·G, finding private key k requires ~2128 classical operations — infeasible. Shor's algorithm on a CRQC solves ECDLP in O(n³), extracting k in seconds.
ML-DSA (Dilithium)
Primary NIST recommendation. Lattice-based. Replaces ECDSA for all agent+jwt and proof+jwt signing.
| Pub key | 1312 B vs 64 B (EC) |
| Signature | 2420 B vs 64 B |
| Used for | AgentToken, ProofToken, OID-FED EC |
ML-KEM (Kyber)
Replaces ECDH in TLS 1.3 key exchange. Protects the transport layer for mTLS agent-to-gateway connections.
| Pub key | 1184 B vs 32 B (ECDH) |
| Ciphertext | 1088 B |
| Used for | mTLS key exchange (Phase 2 mTLS) |
SLH-DSA (SPHINCS+)
Conservative option. Only relies on hash function security. Ideal for Trust Anchor signing (long-lived, high-assurance).
| Pub key | 32 B (small) |
| Signature | 8–50 KB |
| Used for | OID-FED Trust Anchor subordinate statements |
Click each era to explore the token structure, pros, cons, and required migration actions for that phase of the quantum transition.
{
"typ": "agent+jwt",
"alg": "ES256"
}.{
"sub": "spiffe://cloud-a/agent",
"cnf": { "jwk": { "kty":"EC",
"crv":"P-256","alg":"ES256" } },
"exp": ..., "jti": "..."
}
TLS 1.3 ECDHE + ECDSA EC P-256 cert URI SAN: spiffe://...
Small tokens (~64B sig). Fast ES256 verification. Universal library support. Proven in production. No size overhead for JWT payloads.
Vulnerable to Shor's algorithm on CRQC. HNDL: today's traffic collectible for future decryption. Migration window opens ~2028.
| Component | Current | PQ-Safe | Migration |
|---|---|---|---|
| AgentToken (agent+jwt) | ES256 | ML-DSA-44 | Hybrid: both sigs during transition; validators accept either |
| AgentProofToken | ES256 + cnf.jwk binding | ML-DSA-44 + PQ cnf.jwk | cnf.jwk becomes OKP key with ML-DSA curve |
| AgentChain links | Each AT-N: ES256 | Each AT-N: ML-DSA-44 | Chain validator must support both alg types per link |
| OID-FED Entity Config | entity-statement+jwt ES256 | ML-DSA or SLH-DSA for Trust Anchor | Publish both EC and PQ keys in JWKS during transition |
| mTLS agent certs | EC P-256 X.509 | ML-DSA cert + ML-KEM key exchange | Dual-cert: present both EC and PQ cert; server picks best |
| JTI replay store | In-memory (single node) | Distributed (Raft-replicated) | JTI store upgrade is independent of PQ algorithm migration |
| Threat | Vector | Classical Risk | Quantum Risk | Mitigation |
|---|---|---|---|---|
| T-Q1: CRQC Key Extraction | Shor's algorithm extracts EC P-256 private key from public point Q | Low — 2¹²⁸ ops | Critical — seconds on CRQC | Migrate all agent keys to ML-DSA-44; deprecate ES256 |
| T-Q2: HNDL — Delegation Chains | Collect agent+jwt chains today; break sig keys post-CRQC; forge entire chains | None | High — full audit trail exposed + forgeable | Deploy hybrid mode from 2028; PQ-only mandatory by 2032 |
| T-Q3: HNDL — mTLS Sessions | Record TLS handshakes; ECDH session secrets recoverable via Shor's once CRQC available | None | High — all past session secrets exposed | Deploy ML-KEM-768 (X25519MLKEM768) for TLS key exchange |
| T-Q4: OID-FED Trust Chain Forgery | Extract IdP signing key; issue fake subordinate statements; hijack entire federation | Low | Critical — entire trust graph collapses | SLH-DSA for Trust Anchors; ML-DSA for subordinate statements |
| T-Q5: JTI Hash Weakening (Grover) | Grover's algorithm halves SHA-256 pre-image resistance from 256 to 128 effective bits | Negligible | Medium — SHA-256 JTIs weaker but not broken | Upgrade JTI generation to SHA-512 or use 256-bit cryptographic random UUIDs |
| T-Q6: Proof Token Forgery | Recover workload private key via Shor's; generate valid agent-proof+jwt for any URI | Low | Critical — replay protection bypassed for all old proof tokens | Migrate proof token cnf.jwk to OKP/ML-DSA; enforce short proof TTL ≤ 2 min |
cnf.jwk, upgrading to PQ is link-by-link — you don't need to migrate all agents simultaneously. An orchestrator with an ML-DSA key can issue chains to executors that still use EC, and a mixed-alg chain can still be validated if the validator supports both.
A production Agent Fabric IdP cluster runs multiple replicas. Without distributed consensus, the JTI replay store is per-node (replay attacks succeed by hitting different replicas), key rotation creates a split-brain window, and token revocation propagates with unpredictable delay.
| Problem | Without Consensus | With Raft/Paxos |
|---|---|---|
| JTI replay across replicas | Replay hits replica that hasn't seen the JTI | JTI log replicated; replay rejected cluster-wide |
| AgentToken issuance in HA | Two leaders race → duplicate JTIs | One Raft leader issues; committed before returning |
| Signing key rotation | Replicas briefly serve old and new keys | Key swap committed as Raft entry with activation time |
| Revocation propagation | Revoked token accepted by lagging replica | Revocation log replicated; instant cluster-wide effect |
| OID-FED SS cache invalidation | Stale SS used after Trust Anchor update | Cache invalidation event committed as Raft entry |
Proposal value = {sub, chain_depth, cnf.jwk, jti, exp}. Proposer = IdP primary. Acceptors = IdP replicas. Quorum = 2 of 3.
- 13 IdP replicas (or 5 for 2-failure tolerance). Raft leader handles all token issuance writes.
- 2JTI written to Raft log before token returned to caller. All replicas see the same JTI history.
- 3Key rotation committed as a Raft entry with a future activation timestamp. All replicas switch simultaneously.
- 4Gateways subscribe to revocation SSE stream from Raft leader. Revoked JTIs propagated within one RTT.
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.
txntoken+jwt)// Header { "alg": "ES256", "typ": "txntoken+jwt" } // Payload { "iss": "https://tts.example", "sub": "alice@corp", "aud": ["https://api.example"], "txn": "a3f8-b12c-…", // unique txn ID "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 }
| 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.
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", "aud": ["https://as.cloud-b.example/token"], "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", ) // 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"
| 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.
Integrating the x402 HTTP payment protocol with WIMSE Agent Fabric + 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.Comparing four emerging approaches to AI agent authentication and authorization: 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 solves a different layer of the agent auth problem. 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.
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.| 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 eleven IETF and OpenID standards — 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-02 ·
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 |
| 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 |
| 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 | — |
pkg/identity/proof.go (future) |
Alternative to AgentProofToken using RFC 9421 HTTP Message Signatures — tracked as potential replacement for JWT-based proof if WG adopts as primary mechanism | 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.