Warming up the neural circuits...
By the end of this chapter you will:
The 2024 GitGuardian report found 12.8 million secrets leaked on GitHub — a 28% increase year over year. Database passwords, keys, private keys, tokens — all committed to repositories, often by accident, sometimes by developers who didn't know better. Once a secret hits a public repository, it's compromised. You cannot "delete" it — bots clone repositories within seconds of a push. This chapter teaches you to manage secrets like a professional.
You don't keep your house key taped to the front door. You don't make 50 copies and hand them to everyone. You don't leave them on a park bench. When you lose a key, you change the locks — immediately.
Secrets work the same way. Store them in a safe place (secrets manager). Give access to only who needs it (least privilege). Rotate them regularly (change the locks). And if one leaks, revoke it instantly — don't wait.
| Method | Security | When it leaks |
|---|---|---|
| Hardcoded in source code | ❌ Worst | Git push = leaked. Every dev's laptop has a copy. |
.env file committed to git | ❌ | Same as above. .gitignore mistakes happen. |
.env file, not committed | ⚠️ | Leaked via: screenshots, logs, backups, malware on dev machine |
| Environment variables (manual) | ⚠️ | Visible in process listing (ps aux), leaked in error pages, child processes inherit them |
| Environment variables (platform) | ✅ | Vercel/Railway/Heroku env vars — encrypted at rest, access-controlled |
| Secrets manager (AWS/GCP/Vault) | ✅✅ | Encrypted, access-controlled, audited, rotated automatically |
| Secrets manager + KMS envelope encryption | ✅✅✅ | Best — secrets encrypted with keys that are themselves encrypted |
# .env — NEVER commit this file. Add to .gitignore.
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
JWT_SECRET="super-secret-key-change-in-production"
STRIPE_API_KEY="sk_live_..."
# .env.example — DO commit this (without real values)
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
JWT_SECRET="generate-with: openssl rand -hex 64"
STRIPE_API_KEY.env files are plaintext on disk. Anyone with file system access can read them. They're better than hardcoding, but for production secrets (database passwords, API keys, signing keys), use a proper secrets manager or your platform's encrypted environment variables.
// AWS Secrets Manager example
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManagerClient({ region: 'us-east-1' });
async function getDatabasePassword(): Promise<string
Rotating a database password while the application is running:
1. Create a NEW database user/password (user_v2 / password_v2)
2. Add the new credentials to the secrets manager (as a second version or new key)
3. Deploy application code that reads BOTH credentials, tries new first, falls back to old
4. Monitor — all connections using new credentials → no errors
5. Remove old credentials from secrets manager
6. Drop the old database user// Dual-credential rotation
async function createPool() {
const [newCreds, oldCreds] = await Promise.all([
getSecret('database/v2').catch(() => null),
getSecret('database/v1')
If your database is compromised (backup leak, injection), encrypted columns protect the data:
import crypto from 'crypto';
const ALGORITHM = 'aes-256-gcm';
const KEY = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex'); // 32 bytes = 256 bits
Encrypting data with AES-256 is good. But where do you store the AES key? If it's in an , and that leaks, all encrypted data is decryptable. Envelope encryption solves this:
// Simplified envelope encryption with AWS KMS
import { KMSClient, GenerateDataKeyCommand, DecryptCommand } from '@aws-sdk/client-kms';
const kms = new KMSClient({ region: 'us-east-1' });
const MASTER_KEY_ID = 'arn:aws:kms:us-east-1:123456789:key/abc-123
Why envelope encryption matters:
In 2016, Uber suffered a data breach exposing 57 million user and driver records. The attackers found Uber's AWS credentials hardcoded in a private GitHub repository. With those credentials, they accessed Uber's AWS S3 buckets and downloaded unencrypted personal data.
What went wrong:
The fix (implemented after): All secrets moved to HashiCorp Vault. All PII encrypted with envelope encryption (AWS KMS). Automated secret rotation every 90 days.
| Mistake | Why it's wrong | What to do instead |
|---|---|---|
Committing .env to git | Secret is permanently in git history. Bots scan for it within seconds. | .gitignore the .env file. Use .env.example with placeholders. |
| Hardcoding secrets in source | Same as above — git history preserves it forever | Environment variables minimum. Secrets manager for production. |
| Using the same secret everywhere | If dev database password leaks, production is compromised too | Different secrets per environment. Rotate them independently. |
| Not rotating secrets | Secrets that never change = secrets that are eventually leaked through some vector | Rotate every 90 days. Automate it. |
| Storing encryption keys next to encrypted data | Like locking a door and leaving the key under the mat | Envelope encryption: encrypt keys with KMS |
| Plaintext secrets in logs/error messages | console.log('Connected to', connectionString) — includes password | Redact sensitive fields in logging. Never log raw connection strings. |
git-secrets or detect-secrets pre-commit hooks. They scan for patterns that look like API keys, tokens, and passwords. Blocks commits that contain them..env is better than hardcoding but still not secure. It's plaintext on disk. A compromised server = all secrets exposed..env file. Verify the app still works. Add .env to .gitignore. Create a .env.example for teammates.git-secrets or detect-secrets. Scan your repository for accidentally committed secrets. Fix any findings.ssn (Social Security Number) field. Store encrypted in the database. Write a service function that encrypts on write and decrypts on read.Why shouldn't you commit .env files to git? Git history is permanent. Even if you delete the file in a later commit, the secret exists in the history forever. Bots scan public repositories for secrets within seconds of a push.
What's the difference between a .env file and a secrets manager? .env is a plaintext file on disk — readable by anyone with file system access. A secrets manager encrypts secrets at rest, controls access via IAM, and provides audit logging and automatic rotation.
What is encryption at rest? Encrypting data before writing it to disk (database, file system) so that physical theft or backup leaks don't expose the plaintext data.
Explain envelope encryption and why it's better than encrypting directly with KMS. Data is encrypted with a data key (AES-256). The data key is encrypted with a KMS master key and stored alongside the data. Benefits: the master key never leaves KMS (uncompromisable), KMS rate limits don't bottleneck your encryption operations, and rotating the master key only requires re-encrypting data keys, not all data.
How do you rotate a database password without downtime? Create a second database user with the new password. Store both credentials in the secrets manager. Deploy application code that tries both credentials. Monitor for successful connections using the new credentials. Once verified, remove the old credentials and drop the old user.
What would you do if you discovered a production database password committed to a public GitHub repository? (1) Rotate the password immediately — that's the #1 priority. (2) Audit access logs for unauthorized access using the compromised credentials. (3) Force-push to remove the commit from git history (or better: rotate and accept the history is immutable on public repos). (4) Enable branch protection rules and pre-commit secret scanning. (5) Post-incident review: how did this happen? Add safeguards.
Secrets management is foundation-level security. Never commit secrets to git. Use secrets managers (AWS/GCP/Vault) for production secrets, environment variables as the minimum bar. Rotate secrets on a schedule. Encrypt PII at rest with envelope encryption (KMS). Cache secrets in memory — don't fetch on every request. Separate secrets from config. The difference between a bad day (rotate a leaked secret) and a catastrophic day (data breach + regulatory fine) is how you manage secrets.
.env → .gitignore. .env.example → commit.git-secrets, detect-secrets..env file and a .env.example file? .env has real secrets (NEVER commit). .env.example has values (DO commit — documentation for teammates).