Your Data Is in Places You Never Look
If you use an AI coding agent, it has your API keys. Not because you handed them over — because they're in your environment and your files, and it reads those to do its job. The part nobody expects is that it writes them down.
Originally written September 2026 · Updated
On 22 October 2026, people started calling the previous ten days "Black October." The name came from a viral post, not a vendor or a government. In those ten days there were two unauthenticated remote-code-execution flaws in PHP, a CVSS 10 in GitLab, a poisoned release of the LiteLLM gateway, and breaches at Railway, Zeabur and OpenCode.
The incidents are different. The common thread is not. In the ones that matter here, the thing that left the building was a credential — an environment variable, a provider key, a GitHub token. Patching fixed the software. It did nothing for the keys that had already been read.
That is the loud version. The quiet version is on your laptop, and it starts the same way.
Everyone knows the rule: don't paste your API key into a chat window. It's good advice, and it is almost beside the point.
The keys that end up sitting in plain text on your laptop rarely get there because someone typed them. They get there because an agent ran a command that printed one, or opened a file that contained one, or wrote one into a request it was building — and every one of those steps is recorded, because recording them is what makes the agent able to explain itself and resume where it left off.
Nothing below requires carelessness. Each step is a normal thing to ask an agent to do.
How keys actually get written down
Every warning you have ever read is about the third bar.
Proportions measured across six months of one developer's agent sessions — roughly three thousand credentials found sitting in files on a single laptop. Your mix will differ. The shape probably won't.
How it actually happens
Four ways in, one thing that multiplies them, and two places they come to rest.
Four ways in
roughly two thirds of everything
- A command prints your environment. You ask why a service won't connect. The agent runs something diagnostic —
printenv,docker inspect, a CLI's ownsecret list. The key is in the output, and the output is the answer, so it is kept. - The agent reads a file. "Why is my build failing?" is enough. It opens
.envto check one line, and records all of them. - Verbose output quotes your own request back. Not the remote service echoing your credentials — almost none do. It is your own tooling:
curl -vprinting the request it sent, or a traceback carrying the request object, headers and all. Debug output is useful precisely because it shows what you sent. - A config file a tool wrote, not you. The risky ones aren't the files you created. They're the ones an installer or an integration wrote on your behalf — plugin manifests, MCP server definitions, desktop app configs — which hold credentials in plain text by design. You never chose to put a key there, so it never occurs to you that one is there.
One thing that multiplies them
the single most common way a key gets written down
The agent needs to call an API, so it builds a request. Into that request it puts the key it is already holding — as a literal, not a reference — and the whole command goes into the record.
curl -H "Authorization: Bearer sk-proj-<the actual key, in full>" https://api.example.com/v1/runs
This is not a fifth way in. The key arrived by one of the four above; this is what happens to it afterwards, over and over, every time the agent needs it again.
The first read is rarely the only exposure. In the sessions I looked at, values were routinely re-emitted a dozen times; the worst case was a single key written into commands a hundred and seventy times in one sitting. One file opened, one hundred and seventy copies.
Two places they come to rest
the transcript is the obvious one
- The session transcript. Everything above ends up here. It is a plain-text file that outlives the conversation by design — that is what makes resuming a session possible.
- The undo history. Agents snapshot a file before editing it so changes can be reverted. The snapshot keeps whatever the file held at the time, which is why fixing the original is not the end of it.
Why this is worse than it sounds
A key in a chat window feels ephemeral. It isn't. These records are ordinary files on your disk, and they behave like ordinary files:
- They are plain text. Not encrypted, not protected. Any process running as you can read them — including the next tool you install.
- They are backed up. If they're in Time Machine, or a synced folder, they're in more than one place, and cleaning the original doesn't clean the copies.
- They are near your repos. A home directory can be a git repository with a remote; mine is. Nothing has to be tracked for one absent-minded
git add -Ato stage months of transcripts, and the ignore rules you wrote for source code do not automatically cover the agent's data directory. - They are big enough to hide in. Tens of thousands of files, gigabytes of them. You are never going to find this by reading.
- They outlive the key's usefulness. A credential you rotated out of your code last spring is still sitting in the transcript from the day you were debugging it.
What Black October added
One case is documented down to the minute, and it happens to be about a coding agent.
The release pipeline — LiteLLM, 21 October 2026
On 21 October, LiteLLM's release pipeline was compromised and pushed version 1.107.0 — a release of the AI gateway that sat on PyPI for under two hours and in Docker images for under three. It carried compiled malware that steals credentials and loads remote-code execution tools.
The company's own account is worth reading slowly. On the Monday before, it had widened release permissions so two engineers could publish daily bugfix backports. The attacker held three sets of developer GitHub CLI credentials; one belonged to one of LiteLLM's two GitHub organization owners. When branch protection rejected the change, that credential overrode it. A second satisfied the production approval gate, and a third deleted the workflow records afterward. The release was built from a lookalike repository — Berri-AI/litellm instead of BerriAI/litellm — so the official source tag was never the malicious one.
Where did the credentials come from? LiteLLM found an innocuously named account in an internal deployment that was exposed to the public internet, and believes the attacker read the tokens out of its logs. It has not confirmed how the attacker got in, and suspects a recently patched vulnerability. Separately, it states what it observed about the agent itself:
"[W]e have observed specific situations in which Claude Code will embed a user's GitHub CLI OAuth token into a response body, and in situations where response bodies are logged, these tokens may appear in LiteLLM logs."
— LiteLLM incident report, 21 October 2026
That is the whole problem in one sentence, written by the company it happened to: an agent wrote a credential into something it was saying, that something was a log, and the log was a file anyone who reached the deployment could read. Anthropic has not publicly commented on the observation, as of publication. The entry path is still unconfirmed — but the escalation from a logged token to a published backdoor is not.
The platforms holding the same material — 14–21 October 2026
- Railway. Disclosed that an unauthorised party "may have accessed encrypted environment variables and, for some of them, obtained the decrypted contents." It told every user to roll every variable, API key and private registry credential, and paused deployments while it investigated. A blockchain investigator has since linked roughly $371,000 in suspected thefts to wallet keys stored on Railway — that link is the investigator's, not the company's.
- Zeabur. Internal credentials stored in plaintext gave access to 3,218,965 environment-variable records and 321,810 accounts. Cluster admin tokens created in January 2024, with no expiry and no rotation after an August incident, were still valid. The attacker searched for AI service API keys. The platform says it will shut down on 31 December 2026.
- OpenCode Console. A legacy system was breached, exposing OpenAI, Anthropic and Google API keys stored in Console workspaces for 1.4% of Console users. Some reported charges they had not made. Revoke at each provider, not just at OpenCode.
The offensive side — Cairn, October 2026
Cairn, an open-source agent platform, turned up in the hands of at least three threat actors automating attacks on commerce and payment sites. The largest cluster used Chinese and ran more than 1,100 attack projects across 14 dashboards; Reuters reports that actor breached over 100 companies, including John Deere and SharkNinja. Researchers found around 25 Cairn panels left open, and 11% of the hundreds of secrets inside were verified as valid. Agents are not only where credentials end up. They are also a way to go looking for them.
Not everything that week fits this story. PHP2Shell (CVE-2026-109618 and CVE-2026-109619, CVSS 9.2) was a pair of unauthenticated remote-code-execution bugs in PHP, found with frontier AI and fixed on 21 October. GitLab patched an unauthenticated RCE rated 10.0. eMudhra had eight intermediate CA keys generated with too little randomness. UpdraftPlus revoked keys for roughly 400,000 vulnerable WordPress sites. Different failures. And Black October was a disclosure wave, not an attack wave — several of these began weeks earlier. The "70% of the web" figure that travelled with it is PHP's usage share, not a count of compromised sites.
None of it is a story about a model doing something malicious. Each is a credential in a place something else could read — a response body, a log, an environment variable, a workspace. That is the same shape as the problem on your laptop, at a different scale.
What to do about it
In rough order of how much they buy you.
- Reference the key, never the value. This is the single highest-value habit, because it kills the biggest category outright. Write
Bearer $API_KEY, not the key itself — the shell expands it at run time, so the literal never reaches the transcript. A password manager CLI is the least dramatic version:op run --env-file=./.env -- ./deploy.shreadsop://references from that file — references, not values — injects them into the process, and masks them in what the command prints. A secrets manager or plain environment variables injected at launch do the same job. What matters is that the literal string never appears in a command, in a file the agent reads, or in a file you commit. - Give the agent a key that can't hurt you. Scope it to what the task needs, set a spend limit where the provider offers one, give it an expiry, and use a different key per project. A key that can only do one thing is a much smaller problem when it ends up somewhere it shouldn't — and a much smaller problem if the agent uses it more enthusiastically than you intended.
- Tell your agent which files are off limits. Most support a deny list or an ignore file. Put
.env, credential files and key material on it. It won't catch command output, but it closes the easiest route. - Find out where your agent writes. Every one of them keeps transcripts, logs and caches somewhere, and the location is documented. Look it up for the tool you use, then look at how much is there. That number is usually the surprise.
- Scan it, not just your repos. Point an open-source secret scanner (gitleaks or trufflehog) at that directory. Both are read-only. The commands are in Cleaning up is not revoking. Expect the first run to be uncomfortable.
- Check your home directory isn't a git repo. Run
git statusin your home folder. If it answers instead of erroring, add your agent's data directories to.gitignoretoday. - Revoke at the issuer, not in the dashboard. Changing a value in Railway, Zeabur or Vercel does not revoke the old one. Create the replacement, deploy it, confirm it works, then revoke the old key at the provider it belongs to. A platform that holds your variables is a place your variables pass through — it is not where they live.
If you're new to this
You do not need to understand any of the above to be affected by it. If you have ever asked an AI assistant for help with code that connects to something — a website, a database, a payment button — then a password was probably involved, and it is probably written down somewhere on your computer now.
Two things are worth doing. Never put a password or key directly into a file you're editing, even temporarily, even as a test — that is the single habit that causes the most of this. And when you finish a project, change the keys, the way you'd change a lock after handing out spares. It takes ten minutes and it undoes everything above.
Cleaning up is not revoking
You can find these values and scrub them — there are tools for it, and it is worth doing, because it stops the same key being copied forward into every future session.
It does not revoke anything. A credential that spent four months sitting in a transcript has to be treated as exposed, because you cannot prove what read that file. Cleaning up limits the future. Only rotation fixes the past.
The transcript is not the only store. On the machine I measured, the undo history alone held 2,997 verbatim copies of file contents, and the agent's logs and caches came to roughly three gigabytes. A scan pointed only at ~/.claude will miss some of it.
Find them first
Neither scanner alters the files it reads. They report locations; they do not edit, move or delete anything. Run them on the agent's data directories, not just your repositories. On macOS mine also wrote to ~/Library/Logs/Claude and ~/Library/Caches/claude-cli-nodejs. One caution before you try trufflehog: it checks whether each finding still works, which sends candidates to the provider.
# read-only; secrets are redacted
gitleaks dir ~/.claude --redact
gitleaks exits 1 when it finds something; that is the signal, not a failure. --redact keeps the report from quoting full keys — a report that prints them is another copy of the leak. Paths are macOS; on Linux the cache lives under /tmp. Do not pipe the output into a delete command.
Then revoke, at the issuer
Work outward from the widest blast radius. A project key that dies last is a key that was usable longest.
- GitHub. Revoke OAuth app grants and personal access tokens in settings, then check org-owned apps, deploy keys and Actions secrets.
gh auth logoutforgets the token on your machine; it does not revoke it at GitHub. - Cloud. Deactivate, then delete, AWS access keys. Delete GCP service-account keys and API keys. Identify unfamiliar keys before removing them.
- Model providers. Revoke at OpenAI, Anthropic and Google. Keys are usually project-scoped, so revoke the project key, not the account.
- Payments and edge. Roll Stripe live and restricted keys, and Cloudflare API tokens.
- Registries. npm, PyPI and Docker Hub tokens, plus anything held in CI secrets.
- Hosts. If a platform held your environment variables, roll every variable, not only the ones you remember.
Then check what the key did while it was loose: audit logs, billing, new deploy keys, new organisation members. Rotation closes the door. The audit tells you whether anyone walked through it.
Order matters too, and not the order you would guess. LiteLLM's guidance to anyone who ran the poisoned release begins: "Don't start with pip install." Every Python start in an affected environment re-runs the malware, and running pip is a Python start. Removing the file is not the same as proving nothing left.
Sources and notes
Where the numbers come from: a single developer's machine, six months, two AI coding tools — September 2026, before Black October, and not a count of its victims. Tens of thousands of files scanned with provider-specific patterns plus generic rules for bearer tokens, credentials in URLs and private keys. Generic rules catch test and placeholder values too, so treat the totals as an upper bound — the proportions are the point, not the count. No secret values were read or stored at any stage of that work; only the type of credential, its location and its length.
- LiteLLM incident report, 21 October 2026.
- Wiz, on the compromised LiteLLM release, October 2026.
- Railway security disclosure, 21 October 2026.
- Zeabur incident notice, October 2026.
- TechCrunch, on the OpenCode Console breach, 19 October 2026.
- SecurityWeek, on the Cairn abuse, October 2026.
- PHP Foundation security release, 21 October 2026.
- GitLab 19.5.1 patch release, 19 October 2026.
- 404 Media, "Black October", 22 October 2026.
- Axios, on the PHP vulnerability, 22 October 2026.
Figures are as reported on the dates shown. Last updated 23 October 2026.
