Home / Articles / Software
Software

The .env File Is Not a Vault: How to Store Secrets Without Leaking to Git

The .env file is indeed practical for storing API keys, database passwords, and tokens while developing applications. However, practical does not mean safe—especially if the file ends up in a repository, logs, backups, or laptops...

File .env Bukan Brankas: Cara Menyimpan Secret agar Tidak Bocor ke Git

Many application projects start with a simple habit: creating a .env file, putting API keys in it, and then adding environment variable readings to the application. This method is convenient because configuration can be separated from the code. The problem is, this file is often treated like a vault, when in fact it is just a plain text file.

If someone can read the .env file, they might see database credentials, email service tokens, payment API keys, or access to cloud services. OWASP categorizes all of these as secrets, including passwords, connection strings, private keys, and session tokens. ([cheatsheetseries.owasp.org](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html?utm_source=openai))

So, the question is not whether your project is small enough to ignore secret management. The question is: how to keep secrets practical to use, but not easy to leak?

Why is the .env file often misunderstood?

The .env file is typically used to store configurations that differ between developer computers, testing servers, and production. For example:

DATABASE_URL=postgres://user:password@localhost:5432/app
PAYMENT_API_KEY=sk_live_example
SMTP_PASSWORD=secret

This pattern is useful because applications do not need to write passwords directly in the source code. However, the .env file still has several weaknesses:

  • Its contents are usually not encrypted.
  • The file can be copied to backups, synchronization folders, or other laptops.
  • Developers might accidentally run git add . and include it in the repository.
  • Its values can appear in logs, error messages, screenshots, or debugging output.
  • Anyone with access to that machine can read it.

In other words, the .env file helps separate secrets from code, but it does not automatically protect secrets from being read or leaked.

First rule: never commit secrets to the repository

The most basic step is to add the local file to .gitignore:

.env
.env.*
!.env.example

The .env.example file can be kept in the repository, but it should only contain variable names without secret values:

DATABASE_URL=
PAYMENT_API_KEY=
SMTP_PASSWORD=

The goal is for team members to know what configurations are needed without seeing the actual credentials. Project documentation should also explain where each value comes from and which environment uses it.

However, .gitignore is not a time machine. If a secret has ever entered a previous commit, removing the file from the latest commit is not enough. That value may still exist in the Git history, forks, local clones, or repository service caches.

If a secret has leaked, don’t just delete it

A common mistake is to delete the secret line from the code and assume the problem is solved. The correct approach is to assume the secret is already known to others.

  1. Revoke or disable the old secret. Revoke the API key, reset the password, or delete the exposed token.
  2. Create a replacement secret. Do not reuse the same value on other servers.
  3. Check its activity. Look at usage logs, unusual requests, new resource creations, or configuration changes.
  4. Clean the repository if necessary. In certain cases, the Git history may need to be rewritten, but this step must be coordinated as it can disrupt clones and branches of team members.
  5. Document the cause. Did the secret leak through a commit, CI/CD logs, screenshots, or chat messages?

OWASP recommends that secrets can be revoked, rotated periodically, only visible to those who need them, and never written to logs in plaintext. ([cheatsheetseries.owasp.org](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html?utm_source=openai))

Use protection levels according to project scale

Not all projects require a complex secret management system. The important thing is to differentiate between local, CI/CD, and production needs.

For personal projects or prototypes

Use a local .env, ensure it is included in .gitignore, and avoid storing the file in automatically synchronized folders without additional protection. Do not send it via chat or place it in shared documents.

Add a simple check before committing. Some editors and security tools can detect patterns like API keys, private keys, or tokens. If using GitHub, the push protection feature can block certain secrets before they enter the repository and issue warnings when that protection is bypassed. ([docs.github.com](https://docs.github.com/en/enterprise-cloud%40latest/code-security/secret-scanning/enabling-secret-scanning-features/enabling-push-protection-for-your-repository?utm_source=openai))

For small teams

Store secrets in the secret settings of CI/CD platforms or hosting services, not in configuration files sent with the source code. Limit who can view and change them. Separate values for development, staging, and production so that one leak does not expose all environments.

Also, pay attention to pipeline output. Debugging commands that print all environment variables can leak secrets even if your repository is private. A private repository does not mean all team members or automated processes can access all credentials.

For production applications

Consider using dedicated secret management services, such as secret managers from cloud providers or platforms used by the organization. The added value is not just storage, but access management, auditing, rotation, and integration with deployment.

The principle is least privilege: applications should only receive the secrets they truly need. A service that only sends emails does not need credentials to delete a database. OWASP also recommends that organizations have clear processes for storing, distributing, auditing, and rotating secrets. ([cheatsheetseries.owasp.org](https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html?utm_source=openai))

Differentiate between regular configurations and secrets

Not all configuration contents should be treated as secrets. Application names, local ports, display modes, or public service addresses are usually not secrets. Conversely, passwords, access tokens, private keys, and connection strings with credentials should be treated as sensitive data.

If a value can be used to log in, change data, spend balances, or take over services, treat that value as a secret.

This separation helps teams avoid two extremes: assuming everything must be overly secretive or assuming nothing needs protection.

Checklist to do now

  • Ensure .env and its variations are included in .gitignore.
  • Create a .env.example without secret values.
  • Search old repositories for patterns of API keys, passwords, tokens, and private keys.
  • Enable secret scanning or push protection if available.
  • Ensure application and CI/CD logs do not print environment variables.
  • Separate secrets for development, staging, and production.
  • Revoke secrets that have previously entered the repository.
  • Determine who can view, change, and rotate each secret.

The bottom line

The .env file is not a bad choice. For local development, this file is still very useful. What is wrong is treating it as a vault, when it is merely a configuration mechanism.

Start with simple habits: do not commit secrets, do not print them to logs, use different values for each environment, and revoke credentials that have leaked. As applications and teams grow, move management to systems that provide access control, auditing, and rotation. This way, security does not rely on the memory of one developer or a single line in .gitignore.

Sources & further reading

– Rio Yotto @rioyotto