Where an agent should put a credential it was issued
A freshly issued credential belongs in a local environment file that version control already ignores, never in the conversation, which is copied into logs and evaluation sets by default, and never in a client-side bundle. Display-time redaction is cosmetic once the value is on disk, so find the vendor's rotation call before you need it.
Before you start
Three things need to be in place, and putting them in place after the credential exists is worse than doing it first.
- A repository with an ignore file you can write to. Step 2 verifies the ignore rule. If there is no
.gitignoreyet, create it before the credential lands, not after. - The vendor’s rotation procedure, located. Not recalled — opened and confirmed. It is the only recovery path, and hunting for it mid-incident turns a five-minute fix into an hour.
- A clear boundary between server code and client code in whatever the session is editing. If you cannot say which files end up in a browser bundle or an app binary, you cannot honour step 3.
If the credential came from a provisioning flow the agent ran itself — as in Cloudinary’s agent onboarding, where npx @cloudinary/cloud writes CLOUDINARY_URL to ./.env and prints a claim URL — part of this is already handled: the value went to a file rather than to the terminal as a bare string. That flow also locks delivery to the public IP the command ran from, which is its own class of silent failure later on, and the environment expires after 24 hours unless a human claims it.
The procedure
-
Write the credential to a local environment file, not into the conversation.
The default sink for a value an agent has just been handed is the transcript — it echoes the key so the human can see what happened, or quotes it back to justify the next tool call. Treat that as publication. Conversations are copied into logs, evaluation sets and support tickets by default; that is normal platform operation, not a breach. Sensitive information reaching model input and output is a named entry in OWASP’s Top 10 for large language model applications.
So the value goes to a file the agent writes and reads, and nowhere else:
.envat the project root, oneKEY=valueline, and no printing of the file back into the chat afterwards. Confirming the file exists is fine; printing its contents is not.test -s .envanswers the question without moving the secret anywhere.What this costs: the human can no longer scroll the conversation to see the credential, so checking it means reading the file. That is the trade you want.
If the value has already appeared in a message this session, it is exposed. Stop the procedure and go to step 5.
-
Confirm version control ignores that file, before writing anything else.
Creating a credential and committing it are two steps a minute apart, usually performed by the same agent: write
.env, wire it up, run the task,git add -A && git commit. Nothing in that sequence pauses to ask whether the new file should be tracked.Check explicitly, in both directions:
git check-ignore -v .env # exit 0, prints the matching rule: ignored git ls-files --error-unmatch .env # exit 0 means ALREADY TRACKEDThe second command is the one that gets skipped. An ignore rule has no effect on a path git is already tracking, so a
.envcommitted once stays committed through every later.gitignoreedit. If it exits 0, the secret is in history and step 5 applies now, not eventually. -
Keep the secret out of anything that ships to a client.
An API secret must never reach browser or native application code. That rules out writing it into any client-side bundle the same session is editing — the specific risk when one agent holds both the credential and the frontend files, because the mechanical way to make a value reachable from the app is to add it to the env file the bundler reads, and bundlers inline those values at build time.
Framework prefixes exist because this keeps happening: a variable named for client exposure (
NEXT_PUBLIC_,VITE_) is compiled into JavaScript served to every visitor and cached at the CDN edge. A native binary is worse — it ships to devices you cannot reach.The condition that decides the design: if something client-side genuinely has to talk to the vendor, the secret stays on a server route that signs or proxies the request and the client receives the signature, not the key. If nothing client-side needs it, the key never leaves the server environment at all. Provisioning a service from inside an agent session is where this gets settled, because that is when the credential’s scope is chosen.
-
Do not count redaction as a control.
Agent tooling that masks secrets in its output shows you
cldapi:****, and the instinct is to read that as handled. It is not. Redaction applied at display time does not help, because the value was already written to disk by the process that displayed it. Masking happens to a rendered string on its way to a human eye, and none of the leak paths run through that eye.So once the credential exists, check the other places the same process put it:
- shell history, if it was ever exported on a command line —
export KEY=...lands in.bash_historyverbatim - CI job logs, if the wiring step ran there
- the agent’s own transcript file on disk, which is subject to step 1’s reasoning even when the interface redacts
- any error handler that dumps the whole environment
OWASP’s Secrets Management Cheat Sheet covers the storage side of this in detail; the operating rule is that the file on disk is the artefact to protect and the display is irrelevant.
Constraining what the sandbox can reach — egress control — narrows where a leaked value can be sent, but it does not un-leak it. It bounds the damage; it does not replace steps 1 to 3.
- shell history, if it was ever exported on a command line —
-
Have the rotation call ready before you need it.
Recovery for an exposed secret is rotation at the vendor. There is no other move. You cannot unpublish a value once it has left the file, and rewriting git history does not do it either — the old object survives in reflogs, in every clone, in the forge’s API, and in whatever mirrored the push. Until the vendor invalidates the credential, it works.
That makes the rotation path a precondition of issuing credentials, not an incident-response detail. A workflow that issues credentials should know, at issue time:
- where the vendor’s regenerate or revoke control lives — vendor-specific, so read it in the vendor’s own documentation rather than recalling it
- what breaks when the value changes: which deployed services read it, and whether rotating forces a redeploy
- whether the credential has an expiry that bounds the exposure by itself. Some agent-facing flows do — the Cloudinary environment above expires after 24 hours unless a human claims it, so an unclaimed credential leaked late in that window has little life left, while a claimed one has no such backstop.
Where the credential came from changes the blast radius as well. A key from an unauthenticated account-creation endpoint belongs to an account nobody has verified yet, which is a different problem from a leaked production key on a paid environment.
What done looks like
The credential exists in exactly one file on disk. git check-ignore matches that file and git ls-files --error-unmatch fails on it. The value has never appeared in a chat message, a commit, or a client bundle, and nothing in the workflow depends on masked output being safe. The rotation procedure is recorded next to the thing it rotates. If the secret leaks tomorrow, recovery is one vendor call and one redeploy, and both are already known.
Sources
- Cloudinary's agent onboarding, where `npx @cloudinary/cloud` writes `CLOUDINARY_URL` to `./.env` and prints a claim URL cloudinary.com
- OWASP's Top 10 for large language model applications owasp.org
- Secrets Management Cheat Sheet cheatsheetseries.owasp.org
See also
-
How to keep a fan-out agent inside provider rate limits: bounded worker pools, both limit axes, Retry-After handling, and jittered retries.
-
How to have an agent provision a Cloudinary environment mid-session with one npx command, store the credential in a file, and claim it before it expires.
-
How to attribute agent cost and latency to the task that caused it using per-task traces, per-call spans, and cache-aware token accounting.
-
How durable workflow engines persist run position so a crash resumes from the last completed step, what determinism costs, and when a queue is enough.