Fire In Belly

AI Agent Sealed Credential Unwrap: Just-in-Time Grants for Tool Calls

11 min read

Black combination key lockbox mounted on a stone wall, in black and white

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.

  1. 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.
  2. 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.
  3. 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 methods and paths is 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.

DomainHoldsMust not holdJob on each tool call
Grant broker (PDP-adjacent)Policy, subject re-verification, freshness checks, grant mintingCiphertext, DEKs, long-lived upstream secretsDecide whether this tool, subject, resource, and proof may unwrap
Ciphertext holder (CDP / vault controller)Sealed rows, ability to use a grantStanding decrypt without a grant; policy "yes" aloneUnwrap one DEK under grant + EncryptionContext, mint or inject, zero plaintext
Agent runtimePlaceholders, typed tool intents, workload identityReusable upstream secrets, vault network pathPropose the business operation without learning the key
Egress / proxyTransient materialised secret for the approved hopStanding copy of the vaultAttach 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.

FieldRequirementFailure if missing
SubjectRe-verified workload and/or human identity from trusted middlewareAgent-forged identity in JSON
ToolExact tool id / operation from a server-owned catalogGeneric "HTTP tool" with standing secret
Resource bindingOwner, tenant, and resource identifiers mixed into EncryptionContext / AADCiphertext moved across users or rows
FreshnessSigned, single-use proof (jti), short expiry (tens of seconds)Replay of an old allow
Scope deltaAudience, methods, paths, or mint scopes allowed for this callUnscoped materialisation
LeaseHard wall-clock TTL; non-renewable; extension = new grantStanding privilege with ceremony
ResidualImmutable unwrap_requested / granted / denied / used / expired the acting principal cannot rewriteCover-up path
Relation to denyMust not flip an explicit policy or human denySocial-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.

  1. 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.
  2. 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.
  3. 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.

  1. Trusted middleware attaches workload identity and human subject; the model cannot supply those fields.
  2. Policy evaluates the typed operation: tool refund.create, tenant, max amount, destination template api.stripe.com/v1/refunds.
  3. Grant broker re-verifies the subject token, checks a gateway-signed freshness proof (jti single-use, ~30s), and mints a grant whose EncryptionContext subset binds owner=HMAC(tenant), resource=stripe:refund, and optional elicitation id.
  4. 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.
  5. Agent-facing logs show placeholder or lease id, never the secret. Egress control still denies non-allowlisted hosts.
  6. Audit emits unwrap_granted / credential_injected on a trust boundary neither the agent nor the deploy role can rewrite.
  7. 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 controlWhat it ownsWhat sealed unwrap adds
Credential-in-contextAgent never sees reusable keys; exfiltration testsDual-domain per-call unwrap of the broker store itself
BYOK / CMKCustomer-managed encryption for LLM platform dataTool-call credential escrow and unwrap grants
Egress controlAllowlists, receipts, DLP at the network edgeWhen sealed secrets may materialise for that edge
Capability tokensNarrowed authority for multi-hop delegationSecret bytes under escrow, not only scope tokens
Human approval bindingExact pre-action human grantApproval may be required before grant minting; deny stays deny
Break-glass dual controlEmergency privilege widenNot 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:

  1. 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.
  2. 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 jti and EncryptionContext binding is a reminder, not a control.
  3. 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.


About Fire In Belly: Independent senior engineering from Tallinn, Estonia. We design and build custom AI agents, AI workflow automation, and internal tools with the dual-domain unwrap grants, placeholder injection, and fail-closed credential paths described above, at published fixed prices. Schedule a call to discuss your next project.