# Where API keys leak with AI coding agents: the files, the tests and the caps that limit the bill

> Source: <https://stackness.dev/blog/where-do-api-keys-leak-with-ai-coding-agents-files-tests-and-caps>
> Published: 2026-10-30 08:17:27+00:00

# Where API keys leak with AI coding agents: the files, the tests and the caps that limit the bill

The confirmed API key leaks that hit AI coding agent users in October 2026 did not run through a `.env` file. The most likely cause is a key typed inline into an agent config file that sits in the project folder and gets committed: GitGuardian counted [24,008 unique secrets in MCP configuration files](https://www.gitguardian.com/state-of-secrets-sprawl-report-2026) on public GitHub in 2025. Next come keys stored by a hosted service that was breached, as at [Railway](https://stackness.dev/tools/railway) on 20 to 22 October, and keys a build baked into a front-end bundle. Gitignoring `.env` stops none of the three. A hard spend cap on every key is what limits the bill.

## Where do API keys leak with AI coding agents?

API keys leak from an AI coding agent stack through six paths, and the symptom is the same for all of them: a provider bill or usage graph that jumps overnight, or a revocation email from Anthropic, OpenAI or GitHub. The cause decides the fix, so the paths below are ranked by how much public evidence there is for each, with a test and a fix for each one.

The most shared case shows how hard the cause is to pin down. A developer [posted a Google Cloud cost anomaly](https://x.com/JustLingonberry/status/2108982107846619209) of about $88,982 on 10 October, then wrote that another model had told him [OpenAI Codex](https://stackness.dev/tools/codex-cli) published his `.env` to [Cloudflare Pages](https://stackness.dev/tools/cloudflare-pages); that diagnosis is unverified. A [Reddit post](https://www.reddit.com/r/devops/comments/1xhoqdk/the_87k_api_key_leak_had_nothing_to_do_with_env/) of 28 October built on an $87,000 bill gives no source I could find for it.

## The key sits inline in an agent config file you committed

An inline key in a committable agent config is the leak path with the most evidence. Every agent reads a project-level config that is meant to be shared: `opencode.json` for [OpenCode](https://stackness.dev/tools/opencode), `.mcp.json` and `.claude/settings.json` for [Claude Code](https://stackness.dev/tools/claude-code), `.cursor/mcp.json` for [Cursor](https://stackness.dev/tools/cursor), `.codex/config.toml` for Codex, `.gemini/settings.json` for [Gemini CLI](https://stackness.dev/tools/gemini-cli). An MCP server's key pasted into one of them goes to GitHub with the next commit.

[LiteLLM's post-mortem](https://docs.litellm.ai/blog/security-incident-oct-21-2026-attack-chain) shows the whole chain. On 27 May 2026 a developer committed `harnesses/opencode/opencode.json`, holding an admin key for an internal deployment of LiteLLM Agent Platform, an experimental [LiteLLM](https://stackness.dev/tools/litellm) service, to a public repository. A follow-up commit deleted the file about three hours later, but the key was never revoked and the original commit stayed in history. On 10 October an attacker used it, and on 21 October a malicious `litellm==1.107.0` reached PyPI.

**Test:** list the agent configs git tracks, then scan the whole history.

```
git ls-files | grep -E '(^|/)(opencode\.jsonc?|\.mcp\.json|mcp\.json|settings(\.local)?\.json|config\.toml|auth\.json)$'
gitleaks git -v --redact --log-opts="--all" .
```

**Fix:** reference the key instead of pasting it: `{env:VAR}` or `{file:path}` in OpenCode, `${VAR}` in `.mcp.json`, `${env:NAME}` in Cursor's `mcp.json`, `bearer_token_env_var` for an HTTP MCP server in Codex, and `apiKeyHelper` for Claude Code's own Anthropic key. Revoke any key that was ever committed; deleting the file does not remove it from history.

## A hosted service that stores your key was breached

A breached platform leaks keys you never pushed anywhere. Railway [disclosed](https://blog.railway.com/p/preliminary-report-october-security-incident) that between 20 October 13:58 UTC and 22 October 03:32 UTC an attacker decrypted a subset of customers' environment variables using credentials taken from Railway's CI, looking for AI provider keys "to perform distillation attacks". Railway put the exposure at no more than 1.85% of workspaces and told everyone who signed up before 21 October to rotate.

Four days earlier, on the night of 16 October, an intruder reached OpenCode Console, OpenCode's hosted service; OpenCode [reported the next day](https://x.com/opencode/status/2111519637049319818) that the breach affected 1.4% of Console users, all with external provider keys linked. Users reported charges in the hundreds of dollars, a few above $1,000, according to [TechCrunch](https://techcrunch.com/2026/10/19/hackers-hit-ai-coding-tool-opencode-running-up-charges-on-users-ai-accounts/), and one put his losses at $1,700, according to [The Hacker News](https://thehackernews.com/2026/10/opencode-console-breach-may-have.html). The OpenCode command line keeps keys on your machine and was not affected. Deleting a key from Console did not revoke it at the provider.

**Test:** list every dashboard holding a provider key: hosting variables, an agent's cloud console, an LLM gateway.

**Fix:** revoke at the provider, never only in the tool. Give each tool its own key with its own cap, so one breach drains one budget.

## The build baked the key into your front-end bundle

A front-end build copies any variable with a public prefix into the JavaScript every visitor downloads. [Vite](https://stackness.dev/tools/vite)'s docs say `VITE_` variables end up in client code and should not hold secrets; [Next.js](https://stackness.dev/tools/next-js) inlines `NEXT_PUBLIC_` values at build time. A coding agent asked to make an API call from a browser app reaches for those prefixes because they work.

Sandeep Roy [scanned 978 public apps](https://dev.to/sandeep_roy_c4e518c3f2641/i-scanned-978-vibe-coded-apps-1-in-26-ships-a-secret-api-key-to-every-visitor-2nj1) built with [Lovable](https://stackness.dev/tools/lovable), [Bolt](https://stackness.dev/tools/bolt) and [v0](https://stackness.dev/tools/v0) and found a secret in 37 of them, nine of them AI provider keys. He found it first in his own app, after a repository scanner had passed it clean: the key lived in the hosting dashboard and entered the code only at build time. He built the scanner he used, a free open source tool called LeakLock, and I found no one who repeated the count.

**Test:** search the build output, not the repository.

```
grep -rEoh 'sk-(proj|svcacct|ant-api03|or-v1)-[A-Za-z0-9_-]{8}|AIza[0-9A-Za-z_-]{35}' dist build .next/static out 2>/dev/null | sort -u
```

**Fix:** move the call to a server route or an edge function, rotate the key first, and switch off public source maps.

## Agent state files ship inside a package

Agent state files ship inside packages because nothing excludes them by default. When you choose "Yes, and don't ask again" on a Claude Code permission prompt, the command you approved, or a prefix of it, is saved to `.claude/settings.local.json`, inline `Authorization` header or `API_KEY=` prefix included. Lakera [watched the npm feed](https://web.archive.org/web/20260516182335/https://www.lakera.ai/blog/your-ai-coding-assistant-just-shipped-your-api-keys) and reported in April 2026 that of about 46,500 packages, 428 contained that file and 33 files in 30 packages held credentials.

Lakera belongs to Check Point, which sells security tools. Claude Code now adds the file to your global git excludes when it first writes one, which keeps it out of commits. npm does not read git's excludes: in my test with npm 11.5.2, `npm pack --dry-run` still listed the file while git ignored it.

**Test:** see what `npm pack` would publish.

```
npm pack --dry-run 2>&1 | grep -E '\.claude/|\.cursor/|\.codex/|\.opencode/'
```

**Fix:** a `files` allowlist in `package.json`, and never approve a command with a key written into it.

## The agent read a secret and kept it in its transcript

A coding agent runs as your user, so it can open `.env`, `~/.aws/credentials` or another tool's auth file unless a rule stops it. In one developer's [202 test runs](https://dev.to/maria_bcn/do-ai-coding-agents-leak-your-api-keys-3mfm), Claude Code and Codex with no rules opened `.env` in every task, and keys printed by program output ended up in session logs. Claude Code [keeps transcripts in plain text](https://code.claude.com/docs/en/claude-directory) under `~/.claude/projects/` for 30 days by default.

The other leak from this path is prompt injection. Mitiga [showed planted instructions](https://www.mitiga.io/blog/poisoned-coding-test-ai-agent-attack) leading Cursor, with auto-run on, to read `~/.aws/credentials` and send it out over MCP. Mitiga describes it as an incident it observed, without naming the victim, and I found no other documented case of an agent sending out another tool's credentials.

**Test:** scan the agents' own directories.

```
trufflehog filesystem ~/.claude ~/.codex ~/.config/opencode ~/.local/share/opencode --results=verified,unknown
```

**Fix:** in Claude Code, add `Read(./.env)` and `Read(./.env.*)` [deny rules](https://code.claude.com/docs/en/permissions); a `.claudeignore` file does nothing. In Codex, set `shell_environment_policy.ignore_default_excludes = false` so variables named like keys stay out of subprocesses. Shorten transcript retention with `cleanupPeriodDays`.

## An old Google key gained Gemini access

Google API keys starting with `AIza` were long treated as public identifiers for Maps or Firebase. [Truffle Security](https://trufflesecurity.com/blog/google-api-keys-werent-secrets-but-then-gemini-changed-the-rules) showed in February 2026 that the same keys authenticate to Gemini once the Generative Language API is enabled on the project, and found 2,863 live vulnerable keys in public web data. The Register [reported in March](https://www.theregister.com/security/2026/03/03/dev-stunned-by-82k-gemini-api-key-bill-after-theft/4925029) that a three-developer startup was billed $82,314 for a Gemini key stolen in February.

**Test:** in the Google Cloud console, check whether the Generative Language API is enabled and which keys have no restrictions.

**Fix:** restrict every key to the APIs and apps that use it. Google now [blocks unrestricted keys](https://ai.google.dev/gemini-api/docs/api-key) on the Gemini API.

## Spend caps and scopes that limit the damage

A hard spend cap is the one control that limits the bill when a key leaks; an alert only reports it. Anthropic, OpenAI, [OpenRouter](https://stackness.dev/tools/openrouter) and [Google Cloud](https://stackness.dev/tools/google-cloud) now all offer a hard cap; of the four, only OpenRouter caps a single key. AWS has no Bedrock spend cap, only a budget action that attaches a deny policy you write yourself, and a Google Cloud alerts-only budget does not stop traffic.

| Provider | Hard cap | Where it applies | 
|---|---|---|
| [Anthropic](https://platform.claude.com/docs/en/api/rate-limits) | Yes | Organisation or workspace spend limit; not the default workspace | 
| [OpenAI](https://developers.openai.com/api/docs/guides/spend-limits) | Yes, with "Enforce a hard limit" switched on | Organisation or project | 
| [OpenRouter](https://openrouter.ai/docs/api-reference/authentication) | Yes | Each key, with a daily, weekly or monthly reset | 
| [Google Cloud](https://docs.cloud.google.com/billing/docs/how-to/budgets-spend-caps) | Yes, since 13 October 2026 | Each project and service, Gemini API included | 
| LiteLLM virtual keys | Yes, only with a database behind the proxy | Each key, `max_budget` and`budget_duration` | 
| AWS Budgets | No, alerts and actions | An action can attach an IAM policy that denies Bedrock; no built-in cap | 

None of the caps is instant: OpenAI and Google both say small overruns get through, and Google raised one victim's billing tier automatically during an attack. Two backstops help after the fact. [GitHub](https://stackness.dev/tools/github) secret scanning [reports leaked keys](https://docs.github.com/en/code-security/reference/secret-security/supported-secret-scanning-patterns) to OpenAI, Anthropic, OpenRouter, Google, xAI and Groq. And scanners do not cover every key: [Gitleaks](https://stackness.dev/tools/gitleaks) has no OpenRouter rule, and neither Gitleaks nor [TruffleHog](https://stackness.dev/tools/trufflehog) recognises a LiteLLM `sk-` key, so search agent configs for `sk-` values by hand.

On Stackness, as of 30 October 2026, OpenCode is on 3 real public stacks and LiteLLM, Gitleaks and Railway on none ([how I count](https://stackness.dev/about/data-sources)); the keys live in the [AI coding tools developers list](https://stackness.dev/categories/ai-tools).

## If you see this, check that

| You see | Check | Fix | 
|---|---|---|
| A key in a public repo, or a GitHub or provider revocation email | `git ls-files` for agent configs,`gitleaks git --log-opts="--all"` | Revoke the key; switch configs to `{env:VAR}` or`${VAR}` | 
| A bill spike with nothing committed | Every hosting platform and agent console holding a key; Railway and OpenCode Console notices | Revoke at the provider; one capped key per tool | 
| A key visible in the browser | `grep` the build output for key prefixes | Move the call server-side, rotate, drop source maps | 
| A key inside a published package | `npm pack --dry-run` for`.claude/` and friends | `files` allowlist; never approve commands with inline keys | 
| A key in an agent transcript | `trufflehog filesystem ~/.claude ~/.codex` | Read deny rules for `.env` ; shorter retention | 
| Gemini charges on a Maps or Firebase key | Enabled APIs and unrestricted keys in Google Cloud | Restrict every key | 
| Any of the above, before it happens | A hard cap on every key and workspace | Anthropic workspace limit, OpenAI hard limit, OpenRouter key limit, Google spend cap | 

## New posts by email

About one email a week with what we published. No account needed.

## Which of these do you use?

Nobody on Stackness runs Railway yet. Tick what you use and be the first.

[Play Lonny Eagle Amateur Skater 4A pixel art skateboarding game. Signing up opens it straight away.Sign up to play](https://stackness.dev/games/lonny-eagle)
