Secrets Management
debt(d5/e5/b5/t7)
Closest to 'specialist tool catches' (d5), trufflehog/gitleaks reliably detect committed secrets but require explicit setup in CI; without them leaks remain silent.
Closest to 'touches multiple files / significant refactor' (e5), quick_fix says swap to Vault/Secrets Manager and rotate — but once a secret is leaked you must rotate it everywhere it's used, update deploy configs, and rewrite git history.
Closest to 'persistent productivity tax' (b5), secrets management touches every environment (web, cli, queue) and every deploy pipeline; choosing a system shapes ongoing ops work but isn't the system's defining shape.
Closest to 'serious trap' (t7), misconception that .gitignore makes .env safe directly contradicts how git works — the 'obvious' protection is illusory because history is permanent.
Also Known As
TL;DR
Explanation
Secrets (API keys, database passwords, certificates) must never appear in source code, logs, or version control. Tiers: environment variables injected at runtime (basic, sufficient for many apps), secrets managers (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager — audit trails, rotation, fine-grained access), and hardware security modules (HSMs — for cryptographic key material). PHP apps typically read secrets from environment variables injected by the orchestration layer, never from committed .env files.
Common Misconception
Why It Matters
Common Mistakes
- Committing .env to version control even once — git history is permanent; rotate any secret ever committed.
- Logging request parameters or headers that may contain tokens — secrets leak into log aggregators.
- Same secrets across environments — a dev credential breach should not unlock production.
- Not rotating secrets after team member departure — former employees retain access to any secret they knew.
Code Examples
# .env committed to git:
DB_PASSWORD=supersecret123
STRIPE_KEY=sk_live_abc123
APP_KEY=base64:xyz...
# Even with .gitignore added later:
git log --all --full-history -- .env
# Shows every historical version — credentials exposed forever
# Runtime injection — secret never in code:
# docker-compose.yml:
services:
app:
environment:
DB_PASSWORD: ${DB_PASSWORD} # Injected from host env or secrets manager
# PHP reads from environment:
$dsn = 'mysql:host=' . getenv('DB_HOST');
$pdo = new PDO($dsn, getenv('DB_USER'), getenv('DB_PASSWORD'));
# AWS Secrets Manager (for rotation support):
$secret = json_decode($secretsManager->getSecretValue(['SecretId' => 'prod/db'])['SecretString'], true);