| name | secrets-management |
| description | Secrets management. Environment variables, Vault, AWS Secrets Manager, rotation, detection. |
Secrets Management
A secret in version control is no longer a secret.
1. Secret Types
| Type | Examples |
|---|
| API Keys | Stripe, SendGrid, Twilio |
| DB credentials | Username + password + host |
| JWT secrets | Signing keys |
| OAuth secrets | Client ID + Secret |
| Certificates | TLS, mTLS certs + private keys |
| Encryption keys | AES keys, RSA private keys |
2. What Never to Do
NEVER:
├── Commit secrets to Git (even private repos)
├── Log secrets (even partial)
├── Pass secrets in URL query params
├── Store secrets in client-side code
├── Hardcode secrets in source code
├── Use the same secret across environments
└── Use weak secrets (< 32 bytes random)
3. Environment Variable Hierarchy
Local dev: .env.local (gitignored)
Testing: .env.test (gitignored) or CI secrets
Staging: Infra secrets store (Vault, AWS SM)
Production: Infra secrets store (Vault, AWS SM)
.env.example ← Committed (placeholders only, no real values)
.gitignore ← Always includes .env*, *.key, *.pem
4. AWS Secrets Manager
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
const client = new SecretsManagerClient({ region: "us-east-1" });
async function getSecret(secretId: string): Promise<Record<string, string>> {
const response = await client.send(
new GetSecretValueCommand({ SecretId: secretId })
);
return JSON.parse(response.SecretString!);
}
const secretsCache = new Map<string, { value: unknown; expiresAt: number }>();
async function getCachedSecret(id: string, ttlMs = 60_000) {
const cached = secretsCache.get(id);
if (cached && cached.expiresAt > Date.now()) return cached.value;
const value = await getSecret(id);
secretsCache.set(id, { value, expiresAt: Date.now() + ttlMs });
return value;
}
const { DB_PASSWORD, JWT_SECRET } = await getSecret("myapp/production");
5. HashiCorp Vault
vault kv put secret/myapp/db password="supersecret"
vault kv get -field=password secret/myapp/db
vault read database/creds/my-role
vault lease renew <lease_id>
vault lease revoke <lease_id>
import vault from "node-vault";
const client = vault({
endpoint: process.env.VAULT_ADDR,
token: process.env.VAULT_TOKEN,
});
const { data } = await client.read("secret/data/myapp/db");
const { password } = data.data;
6. Secret Rotation
Rotation strategy:
1. Generate new secret (new DB password, new API key)
2. Update secret in store (Vault / AWS SM)
3. Application reads new secret (auto-rotation or restart)
4. Revoke old secret
5. Verify everything still works
Rotation frequency:
├── API keys: every 90 days or on team change
├── DB passwords: every 90 days (auto-rotate with Vault)
├── JWT secrets: every 180 days (requires token invalidation plan)
└── TLS certs: before expiry (cert-manager auto-rotates)
On compromise:
1. IMMEDIATELY revoke compromised secret
2. Generate + deploy new secret
3. Audit logs for unauthorized usage
4. Notify affected parties per policy
7. Secret Detection in CI
- name: Scan for secrets
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
- repo: https://github.com/zricethezav/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
8. Checklist