Environment Variables and Secrets Management for Web Applications
Hardcoded API keys committed directly into source code remains one of the most common, entirely preventable security incidents in web development — and it keeps happening because proper secrets management, while not complicated, requires deliberate habits rather than defaults. Here's how to actually do it right.
Why Hardcoded Secrets Are So Dangerous
Once a secret (API key, database password, encryption key) is committed to version control, it's effectively permanent in your repository's history, even if you delete it in a later commit — anyone with access to the repository's full history can retrieve it. If the repository is ever made public, or access is compromised, or even shared with a contractor who shouldn't retain long-term access, every secret ever committed is exposed, regardless of whether it's still present in the current codebase.
Use Environment Variables, Not Hardcoded Values
The standard solution is storing configuration and secrets as environment variables, injected at runtime rather than embedded in code. Your application reads these from the environment rather than from a hardcoded value in a source file, meaning the actual secret values never need to exist in your version-controlled codebase at all.
Never Commit .env Files
A common pattern is a local .env file holding environment variables for local development. This file should always be included in .gitignore from the very start of a project — committing it even once means the secrets it contained are now in your Git history permanently, requiring history rewriting (a genuinely painful process) to fully remove, rather than a simple future commit.
Provide a Template, Not the Real Values
Include a .env.example (or similarly named) file in version control that lists the expected environment variable names with placeholder or dummy values, so new developers or deployment environments know exactly what configuration is expected, without exposing actual working credentials in the repository itself.
Use Different Secrets for Different Environments
Never reuse production secrets in development or staging environments. If a development database credential is compromised (a much more likely scenario given broader team access and less rigorous security practices in dev environments), the blast radius should be limited to development data — not extend to production systems and real customer data.
Rotate Secrets Regularly and After Any Suspected Exposure
Secrets shouldn't be "set once and forgotten forever." Establish a rotation cadence for sensitive credentials, and — critically — immediately rotate any secret that may have been exposed (accidentally committed, shared insecurely, or potentially compromised through any other means), even if you're not fully certain exposure actually occurred. The cost of unnecessary rotation is minor inconvenience; the cost of not rotating a genuinely compromised secret can be severe.
Use a Dedicated Secrets Management Tool for Production
For anything beyond small personal projects, dedicated secrets management tools (like AWS Secrets Manager, HashiCorp Vault, or your cloud provider's equivalent) offer meaningful advantages over plain environment variables alone: centralized access control, audit logging of who accessed which secrets and when, automatic rotation capabilities, and encryption at rest — capabilities that become genuinely important as team size and the sensitivity of what you're protecting both grow.
Limit Secret Scope and Permissions
Apply the principle of least privilege to API keys and credentials specifically, not just database users generally: if a service only needs read access to a specific resource, don't provision a credential with broader write or administrative access "just in case it's needed later." A compromised narrowly-scoped credential limits the damage significantly compared to a compromised broad-access one.
Scan for Accidentally Committed Secrets
Automated secret-scanning tools can be integrated into your CI/CD pipeline or run as a pre-commit hook, catching accidentally committed secrets before they're pushed (pre-commit) or immediately after (CI pipeline scanning), rather than relying entirely on developer vigilance alone — which, being human, inevitably has occasional lapses under deadline pressure.
What to Do If a Secret Is Accidentally Committed
- Rotate the exposed secret immediately — this is the priority, more urgent than cleaning up Git history.
- Remove it from your current codebase.
- If the repository history needs cleaning (particularly for public or previously-public repositories), tools exist for rewriting Git history to remove sensitive data, though this is disruptive for any collaborators with existing clones and should be done carefully.
- Review access logs for the exposed service to check whether the secret was actually used maliciously during the exposure window.
The Bottom Line
Proper secrets management isn't complicated in principle — keep secrets out of version control, use environment-specific credentials, and rotate regularly — but it requires establishing these habits from a project's very start, since retrofitting proper secrets management onto a codebase with an established history of committed secrets is a genuinely more painful process than doing it correctly from day one.
