The .env file arrived in mainstream software practice as a convenience. A place to put the things that differ between environments - local, staging, production - without baking them into the application code. It was never a security control. It became one by accident, because keeping secrets out of git repositories was a better outcome than the alternative.
It is not adequate. This article is about why, and about the three specific failure patterns that we see recurring across engineering teams of different sizes and disciplines.
The three leak patterns we see most
The first pattern is the committed secret. A developer creates a .env file, adds it to .gitignore, but at some point - during repository initialization, a gitignore misconfiguration, a fast 'git add .' - the file gets committed. The file is caught and removed from the working tree, but it remains in the git history. Three years later, a rotation audit finds a credential that has been in the history since 2021. Nobody knows what it accessed. Nobody knows if it was ever used maliciously.
The second pattern is the forwarded .env file. A developer needs to onboard a contractor quickly. The fastest path to getting the contractor productive is sending them the .env file directly - Slack DM, email, shared Google Doc. The credential is now in a messaging system with no expiry, no access log, and no way to trace how many times it was copied. When the contractor engagement ends, the credential is rotated. Six months later, a different contractor is onboarded the same way, by a different developer, with no memory that a rotation was required last time.
The third pattern is the shared staging secret that became a production secret. A development team has a staging environment with placeholder credentials. The application gets promoted to production. The credentials that were 'just for staging' go with it. The secret was never treated as sensitive. It was committed to a .env.example file, shared in documentation, pasted in Slack. It is now a production credential with a decade-long exposure history.
Why .env.example is not a security control
The standard advice for handling secrets in version control is: put your real values in .env, add it to .gitignore, and commit a .env.example with placeholder values. This is good practice for keeping secrets out of the repository. It is not, in any meaningful sense, a security control.
The .env.example tells you which secrets the application needs. It does not tell you who has access to those secrets, when they were last rotated, whether they were leaked, or how to detect if they were. It is documentation, not access control.
The git history problem
Removing a committed secret from the working tree does not remove it from git history. The commit that added it is still there. Anyone who has ever cloned the repository has a copy of the secret in their local .git directory. Every CI/CD system that has run against that commit has the secret in its build logs.
The correct remediation is to rotate the credential immediately and to consider it permanently compromised. Not to remove it from the current HEAD and hope nobody noticed. Not to rewrite history and force-push, which invalidates every open pull request and every local clone.
The correct prevention is to never commit it in the first place, which requires a system where secrets are not stored in files that live adjacent to code.
What secure env management actually looks like
A secure credential management system has four properties that .env files lack by design: access is logged at the level of individual credential reveals; access can be granted and revoked without rotating the credential itself; there is a clear record of who has access and when they got it; and the secret never lives in a file that can be committed, forwarded, or accidentally included in a build artifact.
This does not require a complicated system. It requires a system where secrets are stored and accessed by name, not by file copy. The application fetches the secret at runtime from the credential store. The developer fetches it for local development through an authenticated CLI that logs the access.
Making the switch without breaking the team
The practical objection to replacing .env files is that the development workflow breaks. Every developer has .env files for every project. The onboarding instructions say to copy .env.example and fill in the values. Replacing that workflow requires updating documentation, updating onboarding, and retraining habits that have been in place for years.
The transition is easier than it sounds when the new system provides a local development experience that is faster than the old one. If a developer can run a single CLI command and have their local environment configured in thirty seconds, with credentials that are automatically rotated and an access log that proves what they accessed and when, the friction of the old system becomes obvious in comparison.
The teams that move the fastest are the ones where a developer experiences their first onboarding through the new system. They never had the habit to unlearn.