← All software basics

Leaked secrets: handling API keys in your code

What an API key is, how keys leak to the internet and to your coding agent, what to do first when one does, and how to have your agent audit your project.

Published
A pigeon in an orange work cap presses a pipe shutoff lever with its wing while orange keys spill from an open joint onto the floor.
Cancel the key; mopping up comes second.

Paste this into your coding agent, in your project. It shows you the risks and waits for your OK before changing anything.

Audit for exposed secrets in your codebase
Audit this codebase for exposed secrets, in the code, the full git history, logs and error responses, and anything sent to the browser. Show me what you find ranked by severity, masking the values, and list the keys I need to cancel and replace myself, including any live key you read during this audit. Wait for my OK before changing anything. Then fix the rest, worst first. Show me how you checked that none remain, and tell me what you couldn't check.

An API key is a password your app uses to call a paid service, so anyone who gets it can spend your money or read your data.

This page covers how keys leak and what to do when one does. The prompt above has your coding agent look for exposed keys in your project.

Before you paste it, swap live keys in your .env for test keys where you can, because the agent reads every value it audits. If a provider has no test keys, use a limited key with a spending cap and replace it afterwards.

TL;DR

  • Keys leak through git history, browser code, logs and your coding agent.
  • Cancel an exposed key at the provider first. Deleting it from your code doesn’t stop it working.
  • A .env file doesn’t protect a value that your build sends to the browser.
  • Give your agent test keys, then have it audit the project and check its fixes against the table below.

What a leaked key costs

These are real-life examples of what can go wrong if an API key leaks, as the builders themselves reported them:

Who What happened Damage
A small team on Reddit A Google Cloud key was stolen, and they don’t know how $82,314 in 48 hours, against a normal $180 a month
An app developer on Reddit A public Firebase key was used to call Gemini About $55,000, which Google later adjusted
A developer on Hacker News A Gemini key was hard-coded while vibe coding About $300
An OpenAI user on Reddit A key ended up on Pastebin A bot used up the spending limit in minutes

A spending limit can cap the damage. Check that yours is a real cap, because some budgets only send alerts.

Where keys leak

Where it ends up How it gets there
Git history Committed once, deleted later
Browser A public prefix, a build setting, or a server value passed to code that also runs in the browser
Logs and error responses Logging or returning a whole error object
Chat and tickets Pasted while debugging or setting up a tool
Your coding agent Read from a file, or printed by a command it ran

Your agent can see your keys too

A coding agent reads files and runs commands to do its job, and what it reads is usually sent to its model provider. Claude Code, for example, also keeps session transcripts on your disk in plaintext for 30 days by default. Asking your agent to find your keys means it will read them.

Blocking .env only goes so far:

Tool What its docs say
Claude Code Deny rules don’t cover a script that opens the file itself. Its sandbox still lets commands read most of your computer by default
Cursor Its ignore file can’t stop the agent’s terminal or connected tools
GitHub Copilot Its file exclusions don’t apply in Agent mode

An agent you deploy inside your app can also be talked into giving a key away. METR, an AI research group, says an attacker found one of its vibe-coded apps with its sign-in broken, asked the app’s agent for its API key, and used about $600,000 worth of free credits over three weeks. Simon Willison’s article explains when a tricked agent can leak data.

To limit what your agent can leak:

  • Give it test keys, and keep live keys in your host’s settings rather than on your machine.
  • Use keys with limited permissions and a spending cap, so a leak costs less.
  • Replace any live key an agent did read, which is the safe answer.

What to do first

To revoke a key is to cancel it at the provider so that it stops working. GitHub’s guide puts this first.

  1. Revoke the old key, even if your app breaks for a few minutes.
  2. Create a new key and put it in your host’s environment settings or a secret manager, not back in the repo.
  3. Check the provider’s logs for use you don’t recognize.
  4. Rewrite git history only for a specific reason, such as customer data in a commit. GitHub calls it time-intensive and often unnecessary once the key is revoked.

Which keys are meant to be public

Scanners and agents can both flag keys that are safe to ship, so check the provider’s docs first. Here are some examples, and this isn’t an exhaustive list:

Provider Safe in the browser Never in the browser
Stripe pk_ publishable keys sk_ secret and rk_ restricted keys
Supabase sb_publishable_ and the legacy anon key sb_secret_ and the legacy service_role key
Google Maps and Firebase keys, if limited to those services Any key that can call Gemini

A public key can still expose data. A Supabase key is meant to be public, but with no access rules on the database it can let anyone read your data. The prompt on this page doesn’t cover that, so ask your agent to check those rules separately.

How keys reach the browser

The name of an environment variable can decide what a framework ships. Next.js copies NEXT_PUBLIC_ values into the JavaScript it sends to the browser, and Vite does the same for VITE_ values.

An env file holds an unprefixed secret key and a VITE_ publishable key. The unprefixed value stays on the server. The prefixed value goes into the browser bundle, where anyone can read it.

The variable’s name decides where the value goes.

Ignoring a .env file keeps it out of git. Once a build copies a value out of the file, that protection no longer applies. The usual fix is a server route that holds the key, which the browser calls.

Why deleting a key isn’t enough

Here’s an example. You commit a key in a .env file, then delete the file in a later commit. The file is gone from your project, and the key is still in the history:

Three commits: init, add .env, then remove .env. The key hangs from the second commit. Listing the files shows no .env, and git show HEAD~1:.env prints the key.

The key stays in the commit that added it.

A scanner such as gitleaks can check your current files or the full history, so make sure a scan covers the history:

# The files you have now
gitleaks dir --redact .
# Every commit
gitleaks git --redact .

A clean scan doesn’t prove a project is clean, because scanners can miss plain-word passwords and keys split across strings.

Have your agent audit your project

An agent can read the git history, run your build, search the output and read your error handlers with the prompt above. The prompt asks for masked values, but the agent still reads them. A useful audit returns rows like these, in this invented example:

Finding Where Severity Who acts
Payment secret key Old commit, .env Critical You revoke it. The agent reads the new one from the environment
Database secret key NEXT_PUBLIC_ variable Critical You revoke it. The agent adds a server route
AI provider key Logged by an error handler High You revoke it. The agent fixes the logging
Payment publishable key Client code None Nobody, it’s meant to be public

What to check in the agent’s fix

If you see It fails because
The key removed, with no list of keys to revoke The key still works
History rewritten and the problem called solved The key still works, and old copies survive
.env added to .gitignore as the whole fix Git keeps tracking a file it already tracks
A secret in a public prefix or passed to browser code The value ships to every visitor
console.error(err) in a request path Error objects can carry request headers
A clean scan as the only proof Scanners miss things, so it also needs a build search and a read of the code

An audit won’t see your host settings or provider logs. Do the revoking yourself, and check your provider’s dashboard to confirm that each old key is cancelled.

Further reading

The best places to go next, easiest first: