Why Your API Keys Keep Leaking
Leaked API keys are one of the most common and costly mistakes in software development. Here is how it keeps happening and how to actually stop it.
Every week, thousands of API keys get accidentally published to the internet. Stripe keys, OpenAI keys, AWS credentials, database passwords. Most of them end up on GitHub, usually within minutes of being pushed, and usually by someone who had no idea it was happening.
This is not a problem that only affects beginners. It happens to experienced developers too. The reason is that the way we build software makes it genuinely easy to do by accident, and the consequences can range from an unexpected bill to a completely compromised application.
What an API key actually is
When your app talks to an external service, it needs to prove who it is. It does this by sending a secret string of characters along with every request. That string is the API key. The service on the other end looks up the key, figures out which account it belongs to, and decides whether to allow the request.
The key is essentially a password for your account with that service. If someone else gets hold of it, they can make requests as if they were you. They can send emails from your SendGrid account, charge cards through your Stripe account, generate text through your OpenAI account, or spin up servers on your AWS account. Whatever the service allows, they can do, billed to you.
How keys end up on GitHub
The most common way is also the most innocent. You are building your app, you need to connect to a service, so you paste the API key directly into your code to get things working quickly. It works. You move on. A few hours later you push your code to GitHub.
Your key is now public. Not just visible to people who go looking for it, but actively indexed by bots that crawl GitHub specifically to find secrets. These bots run continuously, and they are fast. Studies have found that exposed credentials are often picked up within seconds of being pushed.
The second most common way is through the .env file. Most projects use a .env file to store secrets locally, and most projects also have a .gitignore file that is supposed to tell git to ignore .env. But if you created the repository without the right template, or if you added .env to .gitignore after already committing it once, the file might already be tracked by git. Adding it to .gitignore at that point does nothing because git is already watching it.
A third way is through build output. Some frameworks, particularly frontend frameworks, bundle your code into files that get served directly to the browser. If a secret ends up in code that the framework considers part of the frontend, it will be included in that bundle and sent to every visitor who loads your app. This is especially common with environment variables in React or Next.js apps, where variables prefixed with NEXT_PUBLIC_ are intentionally exposed to the browser. Using the wrong prefix by mistake sends a secret to every user.
What happens when a key is found
Automated bots do not wait around. The moment a key is found and validated, it gets used. For OpenAI keys, that usually means a flood of API calls generating text at whatever rate the account allows, running up a bill that can reach thousands of dollars in hours. For AWS credentials, it often means new servers being spun up to mine cryptocurrency, again billed to your account. For Stripe keys, it depends on the permissions attached to the key.
GitHub has a feature called Secret Scanning that detects common key formats and notifies the relevant service provider automatically. Many providers will revoke the key immediately when they are notified. This sounds helpful, and it is, but it does not mean you are safe. The window between exposure and revocation is often long enough for damage to be done. And not every service participates in the program.
How to actually prevent it
The first step is making sure your .env file is in .gitignore before you make your first commit. If you are starting a new project, most framework templates already include this. If you are not sure, check your .gitignore file and make sure .env is listed there.
If you have already committed your .env file at some point, removing it from .gitignore is not enough. Git has a history of every file that was ever tracked. You need to remove it from that history using git rm --cached .env, then commit that change. Even then, the file existed in your history, so any key that was ever in it should be considered compromised and rotated.
The second step is to never put secrets directly in your code. Even temporarily. Even to test something quickly. The habit of pasting a key into code and then removing it later breaks the moment you forget to remove it, or push before you get the chance. Use environment variables from the start.
The third step is to rotate any key you think might have been exposed. Most services make this easy. Go to your account settings, generate a new key, update it in your environment variables, delete the old one. It takes five minutes and it closes the door even if someone already found the old key.
The fourth step, for anything beyond a personal project, is to use secrets management. Tools like Doppler, Infisical, or even something simple like your hosting platform's built-in environment variable storage give you a place to keep secrets that is not your codebase. Your app reads them at runtime from the environment, and they never touch your git history at all.
The scope problem
One thing that makes leaked keys more dangerous than they need to be is that people often use keys with more permissions than their app actually needs. An AWS access key that can do anything on your account is catastrophically more dangerous than one that can only read from a specific S3 bucket.
Most services let you create keys with limited permissions. A key that can only send emails cannot spin up servers. A key that can only read from a specific database cannot delete it. Getting into the habit of creating narrowly scoped keys means that even if one leaks, the blast radius is much smaller.
If it already happened
If you pushed a key to a public repository, assume it has already been found. Revoke it immediately in the service's dashboard, do not wait to see if anything happens. Then check your account for any activity that should not be there. Most services have an activity log.
Then clean your git history to remove the key. This requires rewriting history, which is disruptive if other people have already cloned the repository, but it is the right thing to do. GitHub has documentation on how to do this, and there are tools like git filter-repo that make it more manageable.
Finally, figure out how it happened so it does not happen again. The most durable fix is the one that makes the mistake impossible, not just less likely.
Deploy your app in minutes
Jetpacked handles Docker, HTTPS, databases, and deployments — so you can focus on building.
Launch your app free