Secrets Management
Golden Rules
-
Never commit secrets to version control
-
Never log secrets in application logs
-
Rotate secrets regularly
-
Use least privilege for access
-
Encrypt at rest and in transit
When NOT to Use This Skill
-
JWT/OAuth implementation - Use authentication skills for protocol setup
-
Database security - Use database skills for connection security
-
General OWASP issues - Use owasp-top-10 for broader security
-
Dependency vulnerabilities - Use supply-chain for package security
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: security for comprehensive documentation.
Environment Variables
.env Files
.env (never commit!)
DATABASE_URL=postgresql://user:pass@localhost:5432/db API_KEY=sk-xxx JWT_SECRET=super-secret-key
.env.example (commit this - no real values)
DATABASE_URL=postgresql://user:pass@host:5432/db API_KEY=your-api-key-here JWT_SECRET=generate-a-secure-secret
.gitignore
Secrets - ALWAYS ignore
.env .env.local .env.*.local *.pem *.key credentials.json secrets.yaml
Loading in Code
// Node.js with dotenv import 'dotenv/config'; const apiKey = process.env.API_KEY;
// Validate required vars at startup
const required = ['DATABASE_URL', 'API_KEY', 'JWT_SECRET'];
for (const key of required) {
if (!process.env[key]) {
throw new Error(Missing required env var: ${key});
}
}
Secret Detection
Pre-commit Hooks
.pre-commit-config.yaml
repos:
-
repo: https://github.com/gitleaks/gitleaks rev: v8.18.0 hooks:
- id: gitleaks
-
repo: https://github.com/Yelp/detect-secrets rev: v1.4.0 hooks:
- id: detect-secrets args: ['--baseline', '.secrets.baseline']
Scanning Commands
Gitleaks - scan entire history
gitleaks detect --source . --verbose
Scan only staged changes
gitleaks protect --staged
TruffleHog
trufflehog git file://. --only-verified
GitHub secret scanning (in repo settings)
Enable "Secret scanning" and "Push protection"
Secret Stores
HashiCorp Vault
import Vault from 'node-vault';
const vault = Vault({ apiVersion: 'v1', endpoint: process.env.VAULT_ADDR, token: process.env.VAULT_TOKEN });
const { data } = await vault.read('secret/data/myapp'); const dbPassword = data.data.db_password;
AWS Secrets Manager
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManager({ region: 'us-east-1' }); const response = await client.getSecretValue({ SecretId: 'myapp/db' }); const secret = JSON.parse(response.SecretString!);
Environment-based (Simpler)
// For smaller projects, use encrypted env vars in CI/CD // GitHub Actions, GitLab CI, etc. all support this
// GitHub Actions // Settings > Secrets and variables > Actions > New secret
CI/CD Secrets
GitHub Actions
jobs: deploy: runs-on: ubuntu-latest steps: - name: Deploy env: DATABASE_URL: ${{ secrets.DATABASE_URL }} API_KEY: ${{ secrets.API_KEY }} run: | # Secrets available as env vars ./deploy.sh
# NEVER do this:
# - run: echo ${{ secrets.API_KEY }}
Docker
Never bake secrets into images
Wrong:
ENV API_KEY=secret
Right: pass at runtime
docker run -e API_KEY=$API_KEY myimage
docker-compose.yml
services: app: environment: - DATABASE_URL # From shell env env_file: - .env # From file (not committed)
Secret Rotation
Policy
Secret Type Rotation Frequency
API keys 90 days
Database passwords 90 days
JWT secrets 180 days
Service accounts 365 days
After compromise Immediately
Rotation Process
-
Generate new secret
-
Update in secret store
-
Deploy new secret to services
-
Verify functionality
-
Revoke old secret
-
Update documentation
Detecting Leaked Secrets
If a secret is leaked:
-
Revoke immediately - Don't wait
-
Rotate - Generate new credentials
-
Audit - Check for unauthorized access
-
Clean history - Use BFG or git-filter-repo
-
Post-mortem - Document and prevent recurrence
Remove from git history (use BFG)
bfg --replace-text passwords.txt repo.git git reflog expire --expire=now --all git gc --prune=now --aggressive
Security Checklist
-
.env files in .gitignore
-
.env.example with dummy values committed
-
Pre-commit hooks for secret detection
-
CI/CD secrets in encrypted store
-
No secrets in Docker images
-
Rotation policy defined
-
Least privilege access
-
GitHub secret scanning enabled
Common Secret Patterns
AWS
AKIA[0-9A-Z]{16} aws_secret_access_key\s*=\s*.+
GitHub
ghp_[a-zA-Z0-9]{36} github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}
Generic
password\s*=\s*.+ api[_-]?key\s*=\s*.+ secret\s*=\s*.+ token\s*=\s*.+
Anti-Patterns
Anti-Pattern Why It's Bad Correct Approach
Hardcoding API keys in code Exposed in git history Use .env + .gitignore
Committing .env to repo Public credential exposure Only commit .env.example
Using same secret everywhere Single breach compromises all Use unique secrets per environment
Never rotating secrets Stale credentials vulnerable Rotate every 90 days minimum
Logging full request/response May contain auth tokens Sanitize logs before writing
Storing secrets in frontend Visible to all users Backend only, pass via secure APIs
Quick Troubleshooting
Issue Likely Cause Solution
App can't find env var .env not loaded or wrong name Check dotenv is loaded, verify var name
Secret leaked in git Committed before .gitignore Use BFG to clean history, rotate secret
CI/CD can't access secrets Not configured in platform Add to GitHub Secrets/GitLab CI vars
Gitleaks blocking commit Pre-commit hook found pattern Verify it's false positive or fix
Vault connection fails Wrong token or expired Check VAULT_TOKEN and renewal
Different envs share secrets Copy-paste .env between envs Use separate .env.dev, .env.prod
Related Skills
-
OWASP Top 10
-
GitHub Actions