Skip to content
Rotwise
Security5 min read

You committed an API key to git. Here is what to do now

Rotate, move, purge, prevent: the exact order of operations when a live key lands in a repository, and why deleting the line is the least important step.

You noticed it in a diff, or a scanner flagged it, or GitHub emailed you: a live API key is sitting in a commit. The instinct is to delete the line and push. That is the least important step, and doing it first can make things worse by drawing attention to the commit that still contains the key. Here is the order of operations that actually closes the hole.

1. Rotate the key first, before anything else

Git history is forever. The key exists in every clone, every fork, every CI cache and every backup made since the commit, and deleting the line changes none of that. The only action that ends the exposure is generating a new key at the provider and revoking the old one.

Do it now, before cleaning up the code, and write down the time. If the key was for a payment provider, a cloud account or an email service, check the provider's usage logs for the window between the commit and the rotation. Most of the time nothing happened. You want to know that, not assume it.

2. Move the value out of the code

Read the secret from the environment or from your framework's encrypted credentials, and keep the file that holds it out of git.

echo ".env*" >> .gitignore
git rm --cached .env
git commit -m "Stop tracking environment files"

Commit a .env.example with placeholder values so the next developer knows which variables exist. In Rails, encrypted credentials are the default answer. In a Next.js app, remember that anything prefixed NEXT_PUBLIC_ is shipped to the browser, so a secret with that prefix is public no matter where it lives.

3. Decide whether rewriting history is worth it

Purging the key from history with git filter-repo is possible, and it rewrites every commit after the leak, which means a force push and every collaborator re-cloning. It is worth doing in a private repository with a handful of contributors. It is close to pointless if the repository was ever public or forked, because the copies you cannot reach still have the key. Either way, step one already made the key useless; the rewrite only removes the evidence.

git filter-repo --path .env --invert-paths
git push --force --all

If you do it, tell every collaborator before the force push, not after.

4. Make sure it cannot happen again

  • Push protection. GitHub's secret scanning can block a push that contains a recognized key format. Turn it on for every repository; it costs nothing and it stops the most common case at the door.
  • A scanner in CI. Run a secret scanner such as trufflehog or gitleaks on every pull request, and fail the build on a verified live credential. The verified filter matters: pattern matches alone produce noise, and noise gets ignored.
  • A scanner on your machine. A pre-commit hook catches the key before it becomes a commit, which is the only point where cleanup is free.
  • A prompt rule. If you build with an assistant, tell it in your project instructions that secrets come from the environment, never from constants. The assistant keeps constants where it finds them, so the first one you paste tends to multiply.

Why assistants make this worse

A pasted key was always a risk. What changed is that an assistant reuses whatever it sees. One STRIPE_KEY = "sk_live_..." in a file becomes the same constant in the next file the assistant touches, and then in a test, and then in a README. By the time a scanner runs, the key is in six places and two branches. Rotating early keeps that from mattering; the scanner in CI keeps it from recurring.

The short version

  1. Rotate at the provider and check the usage logs.
  2. Move the value to the environment and stop tracking the file.
  3. Rewrite history only if the repository was never public.
  4. Turn on push protection and add a scanner to CI and pre-commit.

Rotwise runs a secret scanner plus pattern rules on every scan, treats a live key as a P0 finding at the top of the first batch, and declines to "fix" a committed private key with a code patch, because the fix is rotation, not editing. Whichever tool you use, do step one today.

← All posts