Quick path
In this article
Quick read: what changed, why it matters, and what to do next.
A builder is testing checkout. The flow looks right in the browser, but they want the agent to confirm a real PaymentIntent goes through, so they type the obvious request: "create a test charge against Stripe and tell me what comes back."
The agent needs Stripe access to do that. So it asks for the key.
In most setups, that is the moment the whole thing quietly goes wrong. The STRIPE_SECRET_KEY is sitting in a .env file, the agent has read access to the repo, and now the live secret is in the model's context window. From there it travels everywhere context travels: into the prompt, into the provider's logs, into the next tool call, into a transcript that gets pasted into a ticket three weeks later.
The agent did exactly what it was told. The problem is not the agent. The problem is the unit of access.
The wrong unit of access
The default developer pattern is: give the agent the key and tell it to be careful.
That works for a human, more or less, because a human reads the key once, uses it through tooling, and does not narrate it back into a chat log. An agent does not have that instinct. To an agent, a secret key is just a string it now has to reason about, summarize, pass around, and sometimes repeat.
We have already written about where that ends. A misconfigured key in a vibe-coded app turned into an $82,000 credential leak, and that was a human-speed mistake. Agents move faster and leave more copies. The risk is not new, but the blast radius is.
The deeper issue is that the raw key is the wrong thing to hand over in the first place. The agent does not actually want STRIPE_SECRET_KEY. It wants to create a PaymentIntent. Those are different requests. One is a capability. The other is a credential that grants that capability plus a hundred others, with no scope, no allowlist, and no record of who used it for what.
When you hand over the credential, you are trusting the agent to never expose it, never log it, never get prompt-injected into pasting it, and never call the wrong host with it. That is a lot of trust to place in a string sitting in a context window.
The better move is to stop handing over the key at all. The model gets a ticket, not the key.
Cloak as a case note
Cloak is one of the small tools making that idea concrete, and it is worth reading as a case note rather than a product to adopt. Its own README is blunt about what it does: "Use API keys without exposing raw stored keys to the model."
The shape is simple. Keys live in an encrypted vault on your machine. When the agent needs Stripe, it does not get the key. It asks Cloak to make the call. Cloak attaches the key, performs the request, and returns only the result. The value never enters the model.
The mechanism that makes this more than a wrapper is what is missing from the tool surface. Cloak exposes exactly six MCP tools: list_secret_names, get_secret_metadata, sign_request, proxy_authenticated_http_request, mint_short_lived_token, and query_audit. There is no read_secret. No reveal, no get-value, no "just show me the key for debugging." The agent can list secrets, sign with them, proxy through them, and mint short-lived tokens from them. It cannot read them.
The Stripe example from the README is the whole pattern in one call. The agent invokes proxy_authenticated_http_request to create a PaymentIntent. Cloak attaches STRIPE_SECRET_KEY to the outbound request. The key appears nowhere in what the model receives. The agent gets back the PaymentIntent object, which is what it wanted in the first place.
Underneath, Cloak's security invariants spell out why this holds. The daemon owns outbound HTTP; the MCP shim imports zero HTTP clients, so network egress is daemon-owned, not agent-owned (invariant I2). Policy is checked before the vault is even read (I4). Keys are allowlisted by default, so a secret reaches a host only if that pairing was approved. The audit log is hash-chained and tamper-evident, and it all runs locally: no account, no cloud, no telemetry.
The architecture is three parts: a cloak CLI, a cloakd daemon that holds the keys and does the privileged work, and a cloak-mcp server that the agent talks to. The agent never holds the secret because the agent is never the thing that makes the call.
Read it as early signal, not as a finished answer. It is small, it has not had a third-party security audit, and the threat model is deliberately narrow. But the pattern it draws is the part worth keeping.
The secret courier policy
Strip the tool away and what is left is a policy you can write for any agent, with or without Cloak. Call it a secret courier policy: a short, explicit contract that says how an agent is allowed to use a secret it can never see.
It has seven fields.

Here is the checkout case written out:
secret_handle: STRIPE_SECRET_KEY # a name the agent can reference, not a value it can read
allowed_hosts:
- api.stripe.com # the key reaches this host or it does not move
allowed_operations:
- POST /v1/payment_intents # create a charge
- GET /v1/payment_intents/* # read one back
# everything else under this key is denied by default
returned_shape:
- payment_intent.id
- payment_intent.status
- payment_intent.amount
# not: raw upstream headers, not the key, not unrelated account data
audit_record:
- timestamp
- secret_handle
- host
- operation
- caller # which agent / session asked
- result_status
expiry:
session_token_ttl: 15m # the agent's right to ask expires
rotate_on: ["leak_suspected", "weekly"]
fallback:
on_policy_miss: deny_and_log # fail closed
manual_path: "human runs the call from the Stripe dashboard"
Each field closes a specific hole.
The secret handle is the ticket. The agent references STRIPE_SECRET_KEY by name and never holds the value, so there is nothing to leak, log, or paste.
The allowed hosts list is the difference between "can use Stripe" and "can send this key anywhere." A key that only reaches api.stripe.com cannot be exfiltrated to an attacker's endpoint, even if the agent is convinced to try.
The allowed operations turn a credential back into a capability. Creating a PaymentIntent is fine. Issuing a refund, listing every customer, or rotating the account's own keys are different operations that this ticket does not cover.
The returned shape is the field people forget, and it matters. The courier removes the key on the way out, but the response can still carry sensitive data the model does not need. Cloak's own docs are honest about this: proxy_authenticated_http_request returns the upstream body and headers, and mint_short_lived_token returns a derived credential. Cloak cannot prove a remote API never echoes something sensitive back. So the policy names what the agent gets: an id, a status, an amount. Not the full envelope.
The audit record is what lets you answer "what did the agent actually do" without trusting the agent's own summary. Hash-chained is better than appended-to-a-file, but any honest record beats none.
The expiry means a ticket is not a standing grant. The agent's right to ask is time-boxed and the underlying key rotates on a schedule and on suspicion.
The fallback decides what happens when a request falls outside the policy. Fail closed: deny and log. And keep a manual path, because the answer to "the agent can't do this" should sometimes be "a human does it from the dashboard," not "loosen the policy until it works."
That artifact is the actual deliverable. The tool is replaceable. The policy is the thing you own.
What this does not solve
A courier is not a force field, and pretending otherwise is how teams get burned.
Cloak's own README says it plainly: it stops long-lived key leakage, but it does not make a hijacked agent harmless. The threat model is built for a single-user laptop with an OS keychain and kernel peer credentials. Within that scope it defends against prompt injection that asks for stored secrets, vault-file theft without the pepper and recovery seed, rollback and tamper attempts, and untrusted local binaries trying to connect to the daemon.
What it does not defend against is just as important. It will not stop a root or kernel compromise. It will not stop a malicious process running as the same user that can read memory or keychain items directly. It will not stop a user who deliberately pipes secrets somewhere else. And it has not had a third-party security audit.
The subtler limit is the one the policy's "returned shape" field is fighting. Even a perfect courier still hands the agent something on every call. A minted short-lived token is a real credential, scoped and short-lived but real. A proxied response body is real data. The courier guarantees the raw stored key stays put. It does not guarantee that everything an agent touches downstream is safe, because by design the agent has to receive a result to do its job.
This is the same gap we keep running into: keeping the key out of the model is a containment win, but the system prompt is still not a control plane. An agent with a valid ticket can still misuse the access that ticket grants. The courier decides whether the key moves and where. It does not decide whether this specific call is a good idea right now. You want both, and they are different layers.
The ecosystem is feeling for that second layer too. Tools like Lelu frame the problem as authorization at action time: not just whether an agent is allowed to do something, but whether a legitimately authorized agent is being manipulated into doing it. Read together with Cloak, the direction is consistent. Controls are moving out of instructions and into local daemons, policies, allowlists, and audit logs. Treat that as an early, directional signal, not proof of a settled market.
Before the next agent gets a real key
You do not need to install anything this week to act on this. You need to change what you hand over.
Pick one agent that already touches a real secret. The checkout agent, the one that reads from your CRM, the deploy script with the cloud credentials. Then write its courier policy by hand, the seven fields above, before you wire in the next bit of autonomy.
Name the secret handle the agent will reference instead of the value it currently holds. List the hosts that key is allowed to reach. List the specific operations it may perform, and write down that everything else is denied. Decide the shape of the result the agent gets back, and trim it to what the task needs. Name the audit fields you would want after a strange incident. Set an expiry. Write the manual fallback for when a request is correctly refused.
Even on paper, that exercise tends to surface the uncomfortable part fast: most agents today are holding far more than the operation in front of them requires.
If you want help turning that into a working path, that is squarely what we do. BaristaLabs can map a secret courier policy for one real agent workflow through process automation and our data security work. Bring one agent and one secret, and we will leave you with the handle, the allowed hosts, the allowed operations, the result shape, the denied paths, the audit fields, the expiry, and the fallback written down.
The agent does not need your keys. It needs a ticket, a courier that holds the key, and a record of what it asked for. Build that before you give it the real thing.
Review the data security approach
Review the data security approach
See how secret handling, allowlists, and audit trails fit together before agents touch billing, customer, or production systems.
Practical AI Workflow Notes
Want more practical AI operations ideas?
Get short notes on applying AI inside real small-business workflows — from document handling and customer follow-up to internal reporting, compliance, and automation guardrails.
Turn this idea into a pilot
Which workflow should go first?
Use the readiness check to compare impact, effort, risk, owner, and next step before booking a call.
- 3-5 minutes
- Deterministic score
- No sensitive data
Share this post
