AI Agent Sealed Credential Unwrap: Just-in-Time Grants for Tool Calls
Your refund agent needs Stripe for one order. The platform already "solved credentials": every upstream token sits in an encrypted vault table, and a broker injects them at the edge - the same pattern as keeping API keys out of AI agent context. Then a supply-chain RCE lands in the broker. The attacker never talks to the model. They walk the table, unwrap every DEK with the controller's standing decrypt, and leave with every customer's GitHub, Slack, and billing token.
You need a just-in-time sealed credential unwrap contract. Ciphertext stays sealed under a key encryption key the tool mediator cannot use alone. Each tool call gets a short-lived unwrap grant from a separate trust domain, bound to tool identity, resource context, and a freshness proof. The agent sees placeholders, not reusable secrets. Injection or minting happens only after policy allows. Leases are non-renewable. Every unwrap is audited outside model control. The path fails closed when the grant broker or audit plane is down. It sits beside AI agent credential security (keys out of context), BYOK and customer-managed key contracts (platform encryption custody), verifiable egress control (network mediation), attenuating capability-token contracts (narrowed authority), and break-glass dual-control contracts (emergency widening). The job here is narrower: define when sealed bytes become plaintext for one approved tool call without inventing standing decrypt for the broker.
Why "encrypt at rest" still fails the morning-after question
OWASP LLM06:2025 Excessive Agency tells you to limit tool functionality, permissions, and autonomy, then enforce authorization in downstream systems. That advice collapses if the process that attaches credentials can also decrypt every stored token offline.
The IETF Credential Broker for Agents (CB4A) draft (Hartman) states the architectural split plainly: the component that decides "yes" (policy decision point) must never touch credential material, and the component that dispenses credentials (credential delivery point) must never make policy decisions. Compromise of one must not yield the other. CB4A also names the broker as the highest-value target in the architecture and requires fail-closed behavior when the PDP, CDP, vault, or audit trail is unavailable.
NIST SP 800-207 Zero Trust Architecture supplies the same vocabulary outside AI: authentication and authorization are discrete functions performed before a session to an enterprise resource is established, and policy decision is separated from enforcement. Sealed unwrap is that separation applied to secret materialization, not only to network access.
The attack: standing decrypt on an "encrypted" vault
Once teams add a credential broker for agents, three failure modes keep showing up.
- KEK in the same trust domain as ciphertext. The controller holds both the encrypted token rows and the key encryption key (or a Kubernetes Secret that unwraps it). A stolen pod, etcd backup, or RCE yields offline mass decrypt with no audit trail. Solo.io's agentgateway Mode 1 describes this as catastrophic: residual exposure is the entire store.
- Standing cloud decrypt. The KEK lives in KMS or Vault, but the controller retains standing
Decrypt/ transit decrypt. Mass unwrap becomes live, audible, and revocable - yet still not prevented. Mode 2 converts smash-and-grab into a race against your paging channel. - Placeholder without scope. A proxy swaps secrets on the wire so the agent never sees keys - an excellent theft control - while still allowing the agent to choose host, method, or path broadly enough that a materialised key becomes misuse. Keys on the Wire states the boundary explicitly: it stops theft, not misuse; binding
methodsandpathsis the remaining lever.
CB4A's motivation section ties the first two modes to real concentration risk: when AI infrastructure holds long-lived credentials for many providers in one process, compromise of that process yields catastrophic access. Your sealed-unwrap contract exists so "we encrypted the vault" is not a false comfort after that compromise.
Dual domains before any plaintext
Start with trust domains. TTL numbers come later.
| Domain | Holds | Must not hold | Job on each tool call |
|---|---|---|---|
| Grant broker (PDP-adjacent) | Policy, subject re-verification, freshness checks, grant minting | Ciphertext, DEKs, long-lived upstream secrets | Decide whether this tool, subject, resource, and proof may unwrap |
| Ciphertext holder (CDP / vault controller) | Sealed rows, ability to use a grant | Standing decrypt without a grant; policy "yes" alone | Unwrap one DEK under grant + EncryptionContext, mint or inject, zero plaintext |
| Agent runtime | Placeholders, typed tool intents, workload identity | Reusable upstream secrets, vault network path | Propose the business operation without learning the key |
| Egress / proxy | Transient materialised secret for the approved hop | Standing copy of the vault | Attach secret on the wire to a fixed destination template |
Mode 3 in the agentgateway write-up is the concrete shape: the controller loses standing kms:Decrypt; a separate broker mints a short-lived, EncryptionContext-scoped grant; KMS refuses decrypt on owner/context mismatch; the grant is retired after use. CB4A's PDP/CDP split is the same idea at the architecture layer. If your diagram has one box labeled "credential broker" that both decides and decrypts, split it before you decorate it with TTLs.
Unwrap grant field contract
Make the per-call grant machine-checkable. A Slack thread that says "looks fine" is not a grant.
| Field | Requirement | Failure if missing |
|---|---|---|
| Subject | Re-verified workload and/or human identity from trusted middleware | Agent-forged identity in JSON |
| Tool | Exact tool id / operation from a server-owned catalog | Generic "HTTP tool" with standing secret |
| Resource binding | Owner, tenant, and resource identifiers mixed into EncryptionContext / AAD | Ciphertext moved across users or rows |
| Freshness | Signed, single-use proof (jti), short expiry (tens of seconds) | Replay of an old allow |
| Scope delta | Audience, methods, paths, or mint scopes allowed for this call | Unscoped materialisation |
| Lease | Hard wall-clock TTL; non-renewable; extension = new grant | Standing privilege with ceremony |
| Residual | Immutable unwrap_requested / granted / denied / used / expired the acting principal cannot rewrite | Cover-up path |
| Relation to deny | Must not flip an explicit policy or human deny | Social-engineered override |
Safeguard's agent secret-handling patterns describe the broker issuing a short-lived token the model never sees, discarded when the response returns. Keep that property. Add the dual-domain grant so the process holding ciphertext cannot issue "yes" to itself.
Placeholders, minting, and proxy injection
CB4A defines three mediation models. Treat them as an operating menu, not as a product purchase.
- Proxy gateway (strongest isolation). The agent never receives a real credential. The broker injects on the forward hop. Best when credential isolation outweighs the double-hop cost, or when the target cannot mint short-lived tokens.
- Short-lived mint (recommended primary). The CDP mints a derivative token (STS, impersonation, GitHub App installation token, OAuth token exchange) with narrow scope and absolute expiry. The agent may hold a temporary credential; bind it with sender constraints (for example DPoP) where the target supports them.
- Legacy wrap with scheduled revocation (last resort). Handing a long-lived key to the agent with a revoke timer is the weakest model. Flag it in audit logs and treat revocation failure as an incident.
Placeholder-on-the-wire injection - agent sends {{stripe_key}}, proxy swaps real bytes - is a practical Mode A variant for coding agents and MCP servers that keep cleartext tokens in client config. Pair it with fail-closed routing: if the proxy is down, the call fails; it must not pass the request bare.
Worked example: Stripe refund under sealed unwrap
A production refund agent proposes refund.create for order R-4421 at €120.
Standing-decrypt design. The broker loads STRIPE_API_KEY from an "encrypted" table with an in-process KEK, injects it, and the refund succeeds. A week later the same controller image is compromised through a dependency. The attacker unwraps every row offline. The audit line says token_used, and the deploy role can still edit it.
Contract design.
- Trusted middleware attaches workload identity and human subject; the model cannot supply those fields.
- Policy evaluates the typed operation: tool
refund.create, tenant, max amount, destination templateapi.stripe.com/v1/refunds. - Grant broker re-verifies the subject token, checks a gateway-signed freshness proof (
jtisingle-use, ~30s), and mints a grant whose EncryptionContext subset bindsowner=HMAC(tenant),resource=stripe:refund, and optional elicitation id. - Ciphertext holder presents the grant to KMS/Vault, unwraps only that row's DEK, mints or injects a call-scoped credential, zeros plaintext after the hop.
- Agent-facing logs show placeholder or lease id, never the secret. Egress control still denies non-allowlisted hosts.
- Audit emits
unwrap_granted/credential_injectedon a trust boundary neither the agent nor the deploy role can rewrite. - Grant retires; a replayed freshness proof fails closed.
If policy or a human already denied that refund digest, sealed unwrap must refuse. A missing mint path is not a license to fall back to an environment variable.
Fail closed and broker bypass
CB4A's degradation table is the right default: PDP unavailable → fail closed; CDP unavailable → fail closed; vault unavailable → fail closed; audit unavailable → fail closed. Do not keep the workflow alive by reading process.env.STRIPE_KEY.
Bypass prevention is part of the contract, not an afterthought. Agents must not have network or IAM paths to the vault. Prefer infrastructure enforcement - network policy, mesh sidecars, DNS that resolves brokered hosts to the proxy - over hoping a compromised agent will voluntarily use the broker. Monitor target-service audit logs for calls that do not match CB4A-issued tokens or proxy egress.
Emergency loosening of credential scope belongs to break-glass dual control, not to a hidden standing decrypt flag. Routine sealed unwrap stays least-privilege; break-glass is a separate, human-only, time-boxed path.
How this contract relates to adjacent controls
| Adjacent control | What it owns | What sealed unwrap adds |
|---|---|---|
| Credential-in-context | Agent never sees reusable keys; exfiltration tests | Dual-domain per-call unwrap of the broker store itself |
| BYOK / CMK | Customer-managed encryption for LLM platform data | Tool-call credential escrow and unwrap grants |
| Egress control | Allowlists, receipts, DLP at the network edge | When sealed secrets may materialise for that edge |
| Capability tokens | Narrowed authority for multi-hop delegation | Secret bytes under escrow, not only scope tokens |
| Human approval binding | Exact pre-action human grant | Approval may be required before grant minting; deny stays deny |
| Break-glass dual control | Emergency privilege widen | Not a substitute for routine sealed unwrap |
Operating constraints
These constraints are easy to miss when the only deliverables are a vault SDK and a short TTL:
- Separate the grant broker from the ciphertext holder; a single process with standing decrypt turns encrypted-at-rest into offline mass unwrap after compromise. Mode 2 (audited standing decrypt) is a deliberate intermediate posture - not the same as Mode 3 prevention.
- Bind every unwrap grant to tool identity, resource context, and a single-use freshness proof - not only to a short TTL - so stolen grants and replayed proofs fail closed. TTL without
jtiand EncryptionContext binding is a reminder, not a control. - Treat placeholder-on-the-wire injection as theft prevention, not misuse prevention; still enforce destination, method, and path scope after the secret is materialised. A key the agent never saw can still be pointed at the wrong host if the proxy is a generic forwarder.
Promote or hold
Promote when:
- tool credentials are stored as ciphertext the mediator cannot decrypt alone;
- each unwrap requires an independent grant bound to subject, tool, resource context, and freshness;
- agents see placeholders or typed intents, never reusable upstream secrets;
- mint or inject paths zero plaintext after the hop and emit immutable residuals;
- high-risk paths fail closed if grant minting or audit emission is unavailable;
- broker bypass is blocked at the network/IAM layer, not by prompt instructions;
- negative tests prove mass offline unwrap fails without the grant domain, and replayed freshness proofs are denied.
Hold when the broker holds standing decrypt over the whole store, when "encrypted" means a KEK in the same pod, when TTL is a comment, when the agent can fetch_secret(name), when the proxy forwards to agent-chosen hosts, or when audit failure fails open.
What to do next
List every production tool that attaches an upstream credential. For each path, write down where the ciphertext lives, which process can decrypt it today, whether that decrypt is standing or grant-gated, which freshness and resource bindings exist, whether the agent can see plaintext, and what happens when the grant or audit plane is down. Until that inventory shows dual-domain, call-bound unwrap on every privileged path, "we use a credential broker" is an incomplete control - not a finished one.
If you need the control plane implemented as part of an internal workflow program rather than a weekend vault wrapper, start from AI workflow automation with sealed credential unwrap as an explicit deliverable.