Secrets — API keys, database passwords, encryption keys, tokens — are the most valuable targets in your application. A single leaked secret can compromise your entire system. This lesson covers how to store, access, rotate, and revoke secrets properly.
What counts as a secret
Anything that grants access to a system or data:
- Database connection strings and passwords
- API keys and tokens (Stripe, AWS, SendGrid, etc.)
- Encryption keys and signing keys
- OAuth client secrets
- SSH keys and TLS private keys
- JWT signing secrets
- Webhook signing keys
If an attacker had this value, could they access something they should not? If yes, it is a secret.
Where secrets should never be
Source code
The most common mistake. A secret committed to Git is compromised forever — even if you delete it in a later commit. Git history preserves everything.
# DO NOT DO THIS
STRIPE_SECRET_KEY = "sk_live_abc123def456"
Even in private repositories, this is a risk. Private repos get cloned to developer laptops, copied to CI runners, and occasionally made public by accident.
Unencrypted config files
.env files checked into version control, config.yaml with plaintext passwords, application.properties committed to the repo — all common and all dangerous.
.env files are acceptable for local development only and must be in .gitignore.
Client-side code
Anything in your JavaScript bundle, HTML source, or mobile app package is public. API keys in frontend code are visible to anyone who opens the browser's developer tools.
If a frontend needs to call a service that requires an API key, proxy the request through your backend.
Log files
Secrets accidentally logged are secrets compromised. Database connection strings in startup logs, API keys in debug output, tokens in request/response logs — all common patterns.
# BAD — logs the full request including Authorization header
logger.info(f"Request: {request.headers}")
# GOOD — omit sensitive headers
safe_headers = {k: v for k, v in request.headers.items() if k.lower() != "authorization"}
logger.info(f"Request headers: {safe_headers}")
Error messages
Stack traces and error messages that include secrets are a common leak vector, especially in development mode.
Where secrets should be
Environment variables
The simplest approach for many applications:
export DATABASE_URL="postgres://user:password@host:5432/db"
import os
db_url = os.environ["DATABASE_URL"]
Limitations:
- Available to every process in the same environment
- Often visible in process listings (
/proc/*/environon Linux) - No access control, auditing, or rotation support
- Good enough for simple deployments; insufficient for larger systems
Secrets managers
Dedicated secrets storage services:
| Service | Provider |
|---|---|
| AWS Secrets Manager | AWS |
| GCP Secret Manager | GCP |
| Azure Key Vault | Azure |
| HashiCorp Vault | Self-hosted / cloud |
| Doppler | Third-party |
Benefits:
- Access control (IAM policies determine which services can read which secrets)
- Audit logging (who accessed which secret, when)
- Automatic rotation (for supported services like database passwords)
- Versioning (roll back if a rotation fails)
- Encryption at rest and in transit
Example: AWS Secrets Manager
import boto3
import json
client = boto3.client("secretsmanager")
response = client.get_secret_value(SecretId="myapp/database")
secret = json.loads(response["SecretString"])
db_password = secret["password"]
Example: HashiCorp Vault
vault kv get -field=password secret/myapp/database
Secret rotation
Secrets should be rotated regularly and immediately after any suspected compromise.
Automated rotation
Cloud secrets managers can automatically rotate secrets for supported services:
- AWS Secrets Manager can rotate RDS passwords automatically using Lambda functions.
- GCP Secret Manager can trigger rotation via Pub/Sub events.
- Vault supports dynamic secrets — generating short-lived, unique credentials on demand.
Designing for rotation
Your application must handle rotation gracefully:
- Do not cache secrets indefinitely. Re-read secrets periodically or on failure.
- Support dual credentials. During rotation, both the old and new credential should work briefly. Database services and API providers typically support this.
- Test rotation. Rotate secrets in staging before production. A failed rotation in production means downtime.
Secret detection
Prevent secrets from leaking into source control:
Pre-commit hooks
Tools that scan staged files before they are committed:
- gitleaks — fast, configurable, supports custom patterns
- detect-secrets (Yelp) — generates a baseline and flags new secrets
- truffleHog — scans commit history for high-entropy strings
# Install gitleaks as a pre-commit hook
gitleaks detect --source . --verbose
CI/CD scanning
Run secret detection in your CI pipeline as a second line of defence:
# GitHub Actions example
- name: Scan for secrets
uses: gitleaks/gitleaks-action@v2
Repository scanning
GitHub, GitLab, and Bitbucket offer built-in secret scanning that alerts you when known secret patterns (API keys for specific providers) are pushed.
Revoking compromised secrets
When a secret is compromised:
- Revoke immediately. Do not "monitor" a compromised secret. Revoke it.
- Rotate to a new value. Generate a new credential and deploy it.
- Audit usage. Check logs for unauthorised access using the compromised secret.
- Investigate the root cause. How did it leak? Fix the process, not just the symptom.
Most cloud providers and SaaS platforms provide APIs to revoke and regenerate API keys instantly. Automate this process.
Secrets in development
- Use
.envfiles (in.gitignore) for local development. - Use different secrets for development and production. Never share production credentials with development environments.
- Consider tools like
direnvordotenvthat load environment variables per-project. - For teams, use a shared secrets manager (Vault, Doppler, 1Password for Teams) instead of passing secrets through Slack or email.
Summary
Never put secrets in source code, logs, error messages, or client-side code. Use environment variables for simple deployments and dedicated secrets managers for production. Rotate secrets regularly and design your application to handle rotation gracefully. Use pre-commit hooks and CI scanning to catch leaks before they reach the repository. When a secret is compromised, revoke first, investigate second.
