# Secrets Management: Why .env Files in Git Are Still the #1 Rookie Mistake
TL;DR: A committed .env file never really goes away — deleting it in a later commit still leaves the key sitting in git history, readable to anyone with clone access, forever, until you rotate the credential and rewrite history. Fixing this permanently means moving secrets out of files entirely and into a dedicated secrets manager (Vault, AWS Secrets Manager, Doppler, or 1Password CLI, each with real trade-offs), plus pre-commit scanning so it can't happen again.
The mistake, concretely
It looks harmless. You're setting up local dev, you need a database URL and an API key, so you drop them in .env, and at 2am before a demo you run:
git add .
git commit -m "wip: connect stripe"
git push.env wasn't in .gitignore yet. It's in the diff, it's in the commit, and if the repo is public — or becomes public later, or a contractor's laptop with a stale clone gets compromised — the key is out. Deleting the file in the next commit doesn't help:
git log -p -- .envThat command still shows the full contents of every version of .env that was ever committed, including the one you "removed." Git tracks history, not current state. This is the entire mechanism behind almost every "how did they get our AWS key" postmortem.
It's not a hypothetical. GitGuardian's State of Secrets Sprawl 2026 report found 29 million new hardcoded secrets exposed on public GitHub in 2025 alone — a 34% year-over-year increase and the largest single-year jump they've recorded. Even more telling: 64% of secrets leaked back in 2022 are still valid and unrevoked in 2026. The leak isn't usually the failure — the failure is that nobody rotates the credential afterward, so it stays exploitable for years.
Why this keeps happening in 2026
Three reasons, and none of them are "developers are careless":
1. `.env` is the path of least resistance. Frameworks like Next.js, Rails, and Django all read .env out of the box. It's zero-config until it's a zero-day.
2. `.gitignore` is opt-in, not default. A fresh git init doesn't ignore anything. If .env gets created before .gitignore does — which happens constantly when scaffolding a new service — it's one git add . away from being tracked.
3. AI-assisted coding has made it worse, not better. The same GitGuardian report found secret leak rates in AI-assisted repositories running at roughly double the GitHub-wide baseline, with AI-service credentials (OpenAI, Anthropic, and similar API keys) surging 81% year over year as more scaffolding and copy-pasted starter code ships with placeholder keys that get overwritten with real ones and never stripped back out.
GitHub has responded by expanding push protection — as of 2026 it covers dozens of third-party token formats by default (AWS, Datadog, Slack, Supabase, and more) and blocks the push before the secret lands on the remote, even on free public repos. That's a real backstop, but it only catches recognized token formats. A hand-rolled internal API key sails right through.
The fix, step by step
If you've already committed a secret, order matters:
1. Rotate the credential first. Not last, not "after we clean the history." The exposed value should be treated as compromised the moment git push succeeds, regardless of repo visibility. Scrubbing history without rotating just hides evidence — the old key is still valid somewhere in a fork, a CI cache, or an attacker's scraper output.
2. Rewrite history to remove it. Use `git filter-repo`, the tool now generally recommended over the older filter-branch, or BFG Repo-Cleaner for large repos, which runs 10–720x faster than filter-branch:
# git-filter-repo: strip a tracked file from all history
git filter-repo --path .env --invert-paths
# BFG: same idea, simpler flag for secrets specifically
bfg --delete-files .envThen force-push and have every collaborator re-clone — rewritten history breaks existing clones' ancestry.
3. Add real prevention, covered below, so step 1 and 2 don't recur.
Prevention: `.gitignore` is necessary, not sufficient
# .gitignore
.env
.env.*
!.env.exampleThat stops accidental git add . mistakes, not a teammate who force-adds the file or pastes a secret directly into a config value. Pair it with a pre-commit scanner: Gitleaks is the common choice, a single Go binary with no network calls, fast enough to run on every commit without friction.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.2
hooks:
- id: gitleaksRun TruffleHog in CI alongside pre-commit — it scans full git history rather than just the current diff, and verifies whether a detected credential is actually still live, which cuts down on false-positive triage.
Where secrets should actually live
Pre-commit hooks stop the bleeding. They don't solve the underlying problem, which is that a .env file is still a plaintext secret sitting on a developer's laptop or a CI runner's disk. The real fix is a secrets manager that issues credentials at runtime instead of storing them in a file at all. Here's how the common options actually compare, trade-offs included:
| Tool | Best fit | Honest trade-off |
|---|---|---|
| [HashiCorp Vault](https://www.hashicorp.com/products/vault) (v2.1.0 as of September 2026 — HashiCorp jumped straight from 1.21 to 2.0 in April 2026 when it moved to IBM's release and lifecycle model) | Multi-cloud orgs needing dynamic, short-lived credentials (databases, cloud IAM) with fine-grained policy | Powerful but operationally heavy — self-hosting means you now run and patch another stateful service, or you pay for HCP Vault. Steep learning curve for policy/auth-method setup. |
| [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) | Teams already on AWS wanting native rotation for RDS/Redshift/DocumentDB | Priced per secret (around $0.40/secret/month plus API call charges) — costs add up fast at scale, and it's AWS-only, so it's an awkward fit for multi-cloud or on-prem workloads. |
| [Doppler](https://www.doppler.com/) | Small-to-mid teams wanting a fast setup and a clean CLI (doppler run) without standing up infrastructure | No self-hosted option — it's SaaS-only, so you're trusting a third party with the plaintext at rest, and features like audit logs and SSO sit behind paid tiers. |
| [1Password CLI / Secrets Automation](https://developer.1password.com/docs/cli/) | Teams already using 1Password for human credentials who want the same vaults reused via op run in CI/scripts | Feels natural if you're already a 1Password shop; less purpose-built than Vault for dynamic/rotating infra credentials, and it's another paid seat-based product to license. |
None of these is strictly "the best" — Vault wins on control if you have the ops capacity to run it, Doppler and 1Password win on setup speed for smaller teams, AWS Secrets Manager wins on native integration if you're all-in on AWS. What all four give you that a .env file never does: an audit trail of who read which secret and when, and one place to revoke or rotate a credential instead of hunting through every service that has a copy.
A minimal runtime pattern with any of these looks the same regardless of vendor — pull secrets into the process environment at start time, never write them to disk:
# Doppler
doppler run -- node server.js
# 1Password CLI
op run --env-file=.env.template -- node server.jsThe .env.template (or Doppler's config) holds only variable names, committed safely; the actual values are fetched from the vault at runtime and never touch the filesystem.
Secrets rotation: what "best practice" actually means
Rotation isn't just a post-leak cleanup step — it should be scheduled regardless of whether a leak is known to have happened, because you can't prove a negative. A reasonable baseline:
- Database and infra credentials: automate rotation (Vault's dynamic secrets or AWS Secrets Manager's native rotation Lambdas handle this without app changes).
- Third-party API keys: rotate on a fixed interval (quarterly is a common default) and immediately on any team member offboarding.
- Anything that touched a `.env` file that was ever committed: rotate now, not on the next scheduled cycle — treat it as already burned.
If you're evaluating whether your organization's current setup would hold up to this kind of scrutiny, that gap analysis is typically part of a broader cybersecurity review rather than something to bolt on after an incident.
FAQ
Is it enough to just delete the .env file and add it to .gitignore after the fact?
No. Deleting the file in a new commit removes it from the current working tree but not from git history — anyone can still run git log -p or clone the full history and read the old value. You have to rewrite history with git filter-repo or BFG, and rotate the credential regardless.
What if the repository is private — do I still need to rotate the secret?
Yes. Private doesn't mean secure: contractor access, CI logs, cached forks, compromised laptops, and misconfigured repo visibility all leak private repos in practice. Treat "committed" as "exposed" and rotate.
Do I need a full secrets manager like Vault for a small side project?
Not necessarily. For a solo project, .gitignore plus a pre-commit scanner like Gitleaks and a platform-native secret store (Vercel/Netlify environment variables, for instance) is a reasonable baseline. Reach for Vault, Doppler, or similar once multiple services or team members need shared, audited access to the same credentials.
Does GitHub's push protection make .env leaks a solved problem?
It significantly reduces them for recognized formats — GitHub blocks the push before dozens of known token types (AWS, Slack, Datadog, and more) reach the remote, even on free public repos. It won't catch a custom internal API key, so it's a strong backstop, not a replacement for pre-commit scanning and proper secret storage.
Sources
- The State of Secrets Sprawl 2026 — GitGuardian
- GitGuardian: AI-Service Leaks Surge 81%, 29M Secrets Hit Public GitHub
- GitHub Docs: Push protection
- GitHub Changelog: Secret scanning coverage updates, 2026
- HashiCorp Vault releases — endoflife.date
- AWS Secrets Manager pricing
- 1Password CLI documentation — Developer
- git-filter-repo
- BFG Repo-Cleaner
- Gitleaks
- TruffleHog