Protecting API keys in AI apps: 7 mistakes that blow up your bill overnight
TL;DR
Never put API keys in the frontend, the repo or screenshots. Keep keys on the server or in a secret manager, call models through your backend, set spend limits, separate keys per environment and rotate them. Key leaked → revoke first, investigate second.
A familiar story in developer groups: you ship an AI app on Friday night and on Monday morning there’s an API bill for several thousand dollars. The cause is almost always the same — the API key leaked, and someone (usually an automated scanner) used it all weekend. This article covers the seven most common mistakes, how to fix each one, how to stop coding agents from reading secret files, and what to do once a key has leaked.
Why AI keys leak faster than you think
An LLM provider’s API key is like cash: whoever holds it can spend it immediately, with no login and no verification. Bots constantly scan public GitHub, website JavaScript bundles and unpacked mobile apps for key-shaped strings. A key pushed to a public repo can be abused within minutes. For AI apps the damage isn’t only money: strangers can use your account to generate abusive content and get the account suspended.
7 common mistakes and how to fix them
| Mistake | Why it’s dangerous | Fix |
|---|---|---|
| 1. Key in frontend code / mobile app | Anything shipped to a browser or bundled into an app is readable — minified or obfuscated or not | Call the model from your backend or a serverless function; the client calls your API with user authentication |
2. Committing .env to Git | Deleting it later leaves it in history; forks and clones already have copies | Add .env* to .gitignore; turn on secret scanning and push protection on GitHub |
| 3. One key for every environment | Leaking the dev key leaks production; you can’t tell where costs come from | Separate dev / staging / prod / CI keys with clear names; one Workspace per environment if possible |
| 4. No spend limit | Nothing stops the bill when the key is abused | Set spend limits and email alerts in the Console for each Workspace |
| 5. Pasting keys into chat, tickets, screenshots | Slack, Jira and screenshots live long, are widely seen and can be indexed | Share through a secret manager / vault (1Password, Vault, AWS Secrets Manager…) |
| 6. Coding agents can read files containing keys | Keys can end up in context or logs, or be exfiltrated via prompt injection | Block secret files with permission deny rules, hooks or a sandbox (see below) |
| 7. Never rotating keys | Keys from former staff, old laptops and old CI stay alive | Rotate on a schedule and immediately when someone leaves or a leak is suspected |
The right architecture: the client never touches the key
[Browser / App] --(user's login token)--> [Your backend]
| check permissions, per-user rate limits,
| log cost per user
v
[Claude API] (key lives on the server / in a secret manager)
The backend is where you’re in control: who may call, how often, and which prompts are allowed. It’s also where per-user rate limits belong, so one hijacked account can’t burn the whole budget.
Turn on GitHub’s safety net
- Secret scanning scans repositories for strings that look like keys from supported providers and raises alerts.
- Push protection blocks a
git pushthat contains a detected secret, before it reaches the server. The pusher must fix the commit or record a bypass reason. - These are available for public repositories; for private organisation repositories, check your GitHub plan.
Treat this as a safety net, not the main strategy — tools only recognise known key formats.
Stop coding agents from reading secret files
With Claude Code, you can declare deny rules in .claude/settings.json (commit it so the whole team gets them):
{
"permissions": {
"deny": [
"Read(**/.env)",
"Read(**/.env.*)",
"Read(./secrets/**)"
]
}
}
Three things to know so you don’t get a false sense of security:
- Denying
Readdoesn’t block Bash. A command likecat .envcan still read the file if approved. Keep Bash on ask-first, or deny those commands as well. - Read and Write are separate rules. Blocking reads doesn’t block overwrites.
- For stronger guarantees, turn on Claude Code’s sandbox (
/sandbox) for OS-level filesystem and network isolation, or run inside a dev container. In organisations, put critical deny rules in managed settings so users can’t override them.
The best approach is still: developer machines don’t hold production keys. Developers use their own low-limit keys; production keys live only in the runtime environment’s secret manager.
When a key has leaked: follow this order
- Revoke the key in the Console immediately — before anything else. Every minute of waiting costs money.
- Create a new key and update your secret manager and the environments that need it.
- Review usage logs to assess the damage: when the anomaly started, which models were called, how many tokens.
- Purge the key from Git history if needed — but treat the old key as permanently lost, because someone has already copied it.
- Contact the provider about unusual charges, with timestamps and evidence.
- Write a short postmortem: how it leaked, why it wasn’t caught earlier, which safeguard you’re adding.
Pre-deploy checklist for AI apps
- No keys in the frontend bundle or mobile app (search your build output for key strings).
.env*is in.gitignore; push protection is on.- Production keys live in a secret manager, separate from dev and CI keys.
- Spend limits and alerts are set for the production Workspace.
- The backend has per-user rate limits and cost logging.
- There’s an owner and a process for rotating keys.
How to apply it
- Developers: Run the checklist above against your current app today — 15 minutes that can save you a surprise bill.
- Tech Leads: Add secret-file deny rules to every repo’s
.claude/settings.json, and add “no secrets in the client” to the code-review checklist. - PMs: Require every AI project to have spend limits and a named cost-alert recipient from the first sprint, just like monitoring.
Sources & references
- Security — Claude Code Docs
- Claude Code Permissions — Pete Freitag
- Working with secret scanning and push protection — GitHub Docs