← Toutes les actualités
security5 min de lecture

How to detect exposed .env files in a GitHub repository

Par Seaworthy · 11 mai 2026

Green text on a black terminal screen showing code output, suggesting a developer working with command-line tools

Photo by Christina @ wocintechchat.com on Unsplash

Why .env files keep ending up on GitHub

I've seen this happen so many times that I've stopped being surprised by it. A developer creates a .env file locally, writes some database credentials into it, and then at some point, accidentally or through muscle memory, git add . gets run. The file is committed. The push goes up. Five minutes later those credentials are sitting in a public repository, indexed by GitHub's search, and possibly already scraped by one of the automated tools that trawl for this sort of thing.

Sometimes it is worse than that. The .env file gets removed from the working tree but it is still in git history. The developer thinks the problem is solved. It is not.

This article covers how to detect the exposure, how to actually clean it up, and how to make sure it does not happen again.

Finding a .env file in current repository state

The first thing to check is whether a .env file exists anywhere in the current HEAD of the repository.

git ls-files | grep '\.env'

This only shows tracked files at HEAD. It is the fastest check and catches the most common case: someone forgot to add .env to .gitignore before their first commit.

If you want to be more thorough and check all subdirectories:

find . -name "*.env" -not -path "./.git/*"

That will also catch files like .env.local, .env.production, and .env.staging, which are just as dangerous.

Searching git history for .env files

This is where most people stop looking, and it is a mistake. Removing a file from HEAD does not remove it from history.

git log --all --full-history -- "**/.env"

If that returns commits, those commits contain the file and its contents. Anyone who has cloned the repo, or anyone who uses GitHub's API, can retrieve them.

To see the actual content at a specific commit:

git show <commit-hash>:.env

And to get a full picture of what secrets were in the file at that point:

git log --all -p -- .env

The -p flag shows the diff for every commit that touched .env. Scroll through it and you will quickly see if credentials were ever committed.

Searching GitHub with the API

If you are auditing a GitHub organisation rather than a single repo, GitHub's code search can find .env files across repositories. This is also how attackers do it.

gh search code --filename .env "DB_PASSWORD" --owner your-org-name

The GitHub CLI (gh) wraps the search API cleanly. You can substitute DB_PASSWORD for any secret pattern you are looking for: SECRET_KEY, API_KEY, STRIPE_SECRET, and so on.

Be aware that GitHub code search indexes the current default branch. It will not surface secrets that have been removed from HEAD but still live in history.

Actually removing secrets from history

Removing a file from the current commit is not enough. You need to rewrite history, which is disruptive on any branch other people are using. There are two main tools.

git filter-repo is the current recommendation. It is faster and safer than the older git filter-branch:

pip install git-filter-repo
git filter-repo --path .env --invert-paths

That rewrites every commit in the entire repository history to exclude .env. After this, you must force-push every branch and every tag. Anyone who has cloned the repository will need to re-clone.

GitHub also has a "cached views" problem: even after a force-push, the old content may be served from GitHub's cache for a short window. Contact GitHub support to request a cache flush if you are dealing with a genuinely sensitive exposure.

Whatever you do: rotate the credentials first. Do not wait until the history is clean. The secrets are already out; assume compromise.

Preventing it in the future

The cleanest prevention is a .gitignore entry before the first commit:

# Secrets
.env
.env.*
!.env.example

The !.env.example line explicitly allows committing a template file with placeholder values, which is good practice for documenting what variables a project needs without leaking the actual values.

Beyond .gitignore, a pre-commit hook can catch the file even if .gitignore is misconfigured. The pre-commit framework has a detect-secrets hook that scans for high-entropy strings and common credential patterns before the commit lands.

pip install pre-commit detect-secrets

Then in .pre-commit-config.yaml:

repos:
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets

This will not catch every possible secret format, but it catches the most common ones: AWS keys, Stripe keys, generic high-entropy strings, and common variable names like password and secret.

What actually gets leaked

People often assume an exposed .env file means a leaked database password and nothing else. In practice a single .env file frequently contains: the database URL with username and password, a JWT signing secret, third-party API keys (Stripe, SendGrid, Twilio), an S3 bucket name with access credentials, and sometimes admin panel URLs or internal service endpoints.

That is not a list of three things for rhetorical effect. It is a literal inventory of what I have seen in real .env files found via GitHub search.

The blast radius of one committed .env file can be enormous. Which is why the rotation step matters so much. Do not clean the repository and call it done. Treat every secret in that file as compromised.

Seaworthy's data exposure checks flag committed .env files, high-entropy strings in source code, and hardcoded credential patterns as part of its static analysis scan.

Cet article a été généré par IA et résume des sources publiquement disponibles.

Par les créateurs de Seaworthy

Seaworthy analyse votre dépôt pour détecter les problèmes abordés dans des articles comme celui-ci.

Failles de sécurité, secrets exposés et mauvaises configurations détectés avant le déploiement. Gratuit, aucun compte requis.

$ npx seaworthycode