DEV Community

Cover image for You accidentally pushed your .env to GitHub. Here's what to do next.
DebtDraft
DebtDraft

Posted on

You accidentally pushed your .env to GitHub. Here's what to do next.

You pushed your .env file to GitHub. Maybe you noticed a minute later, maybe a week later. Either way, you open the repo, see your database password sitting in plain text, and your stomach drops.

Your first instinct is to delete the file and push again. Don't stop there. Deleting the file doesn't remove it from your Git history, so the secret is still in an old commit. Anyone who cloned or forked the repo in the meantime still has it.

The good news is that the fix is a short list of steps, and the order matters. Here is what to do next.

Step 1: Rotate the key before anything else

Treat the secret as public from the moment it was pushed. Go to wherever it came from (your database host, your cloud provider, Stripe, OpenAI, whatever the key belongs to), revoke it, and create a new one. Then update the places that use it: your deployment settings, your CI secrets, your local .env.

This comes first for a simple reason. GitHub's own guide to removing sensitive data says you need to revoke and/or rotate the secret, and that once you do, the leaked credential can't be used anymore. Sometimes that alone solves the problem.

Rewriting history is slow and cannot be fully undone. Rotating takes a few minutes. Do the fast thing first.

Step 2: Remove .env from your Git history

Now clean the repository so the file is gone from every commit. GitHub recommends a tool called git filter-repo for this. Install it (GitHub's guide asks for version 2.47 or later), then work in a fresh clone, not your usual working copy. git filter-repo actually refuses to run in a copy that isn't fresh unless you force it, because a fresh clone is the easiest thing to throw away if something goes wrong.

git clone git@github.com:your-name/your-repo.git cleanup
cd cleanup
Enter fullscreen mode Exit fullscreen mode

To remove the whole file from every commit:

git filter-repo --sensitive-data-removal --invert-paths --path .env
Enter fullscreen mode Exit fullscreen mode

Use the file's full path in the repository. If the file lived at other paths in the past, for example server/.env, add those paths too, or run the command again.

If you'd rather keep the file but wipe out specific strings, put them in a text file kept outside the repository folder, and use --replace-text instead:

git filter-repo --sensitive-data-removal --replace-text ../secrets.txt
Enter fullscreen mode Exit fullscreen mode

Then check that it worked. This should print nothing:

git log --all --oneline -- .env
Enter fullscreen mode Exit fullscreen mode

Why not git filter-branch?

You will see older answers on the internet that use git filter-branch. Skip them. The Git project itself recommends git filter-repo over it, and the git filter-repo documentation says the problems with filter-branch can't be fixed without breaking compatibility, so it recommends you stop using it.

Step 3: Force-push, then ask GitHub Support to clear the rest

When you're happy with the result, push the rewritten history back:

git push --force --mirror origin
Enter fullscreen mode Exit fullscreen mode

If Git says origin doesn't exist, add the remote back with git remote add origin <your-repo-url> and push again. Two things you may see:

  • Rejected pushes to refs/pull/: these are expected. GitHub keeps its own references for pull requests, and you can't overwrite them from outside.
  • Other rejected branches: if you have branch protection on, you may need to switch it off temporarily, push, and switch it back on.

The rewrite becomes irreversible after this push, so it's worth a pause. git filter-repo records what it changed in .git/filter-repo/changed-refs. If the number of affected refs is higher than you expected, discard the clone and start over before you push.

Then contact GitHub Support through their support portal. Tell them the repository name, how many pull requests are affected, and the first changed commit shown in the git filter-repo output. Support can clear pull request references and cached views of the old commits. Be aware that they only handle sensitive data, not ordinary mistakes, so say clearly what was leaked.

Step 4: Deal with forks and teammates

There are two groups of people you can't fix with a command.

Forks. If anyone forked your repo before you cleaned it up, their copy still has the old history. GitHub can't give you contact details for fork owners, so you'll have to ask them to delete their fork or remove the data. This is another reason Step 1 matters. If the key is dead, an old fork is harmless.

Teammates. Everyone who cloned the repo before the rewrite should either re-clone it or carefully clean their local copy. If they have branches built on the old history, they should rebase them. Merging can bring the removed data straight back.

Step 5: Stop it happening again

Once the incident is over, put some guard rails in place.

Add .env to .gitignore before the next commit.

echo ".env" >> .gitignore
Enter fullscreen mode Exit fullscreen mode

Also be careful with git add . and git commit -a. They are the easiest way to sweep up files you never meant to include.

Turn on GitHub push protection. Push protection blocks a push that contains a recognized secret before it reaches the repository. For public repositories on GitHub.com it is on by default at the user level. For private repositories, an admin has to enable it, and it requires GitHub Secret Protection. If you choose to bypass it, GitHub asks for a reason: "it's used in tests" and "it's a false positive" close the alert, while "I'll fix it later" leaves an open alert behind. That last one is a useful reminder to actually fix it.

Use GitHub secret scanning. Secret scanning runs automatically and for free on public repositories. It raises an alert in the repository when it finds a credential, and GitHub's advice on receiving an alert is the same as ours: rotate it immediately. For private repositories it is part of GitHub Secret Protection, which requires a GitHub Team or Enterprise Cloud plan.

Run a scanner on your own machine or in CI. Tools like gitleaks and trufflehog look through your files and your history for things that look like secrets. Both can run as a pre-commit check or in CI. Check their documentation for the current commands, since the flags change between versions.

One more layer: check every pull request

Push protection and local scanners work best before a secret leaves your machine. A second check on the pull request itself is a useful backup.

We built DebtDraft for this kind of check. It is a GitHub Action that flags hardcoded secrets on a pull request's changed lines, along with other code health checks. The scan runs on your own runner, and secrets stay there. It won't stop the push, so use it alongside push protection, not instead of it. It's free for up to 3 repositories: debtdraft.dev

The short version

  1. Rotate the key.
  2. Rewrite history with git filter-repo in a fresh clone.
  3. Force-push, then contact GitHub Support.
  4. Tell forks and teammates.
  5. Add guard rails so it doesn't happen again.

Everyone pushes a secret at some point. The people who handle it well are the ones who rotate first and ask questions later.

Top comments (0)