Secrets Exposure
Secrets exposure happens when sensitive credentials—API keys, database passwords, private keys—are accidentally leaked. This is one of the most common and easily exploitable vulnerabilities, often leading to complete system compromise within minutes of exposure.
Attackers actively scan GitHub, npm packages, and client-side JavaScript for exposed secrets. Automated bots can exploit leaked AWS keys within seconds of a commit.
Common Exposure Vectors
Hardcoded in Source Code
// VULNERABLE: Secrets directly in code
const stripe = new Stripe('sk_live_abc123xyz789');
const db = new Client({
host: 'prod-db.example.com',
password: 'super_secret_password_123'
});
// Even "temporary" secrets often end up in production
const API_KEY = 'AIzaSyC_temp_key_for_testing'; // TODO: move to envCommitted to Git History
Even if you remove a secret from code, it remains in git history forever:
# Attacker searches git history for secrets
git log -p | grep -i "api_key\|password\|secret\|token"
# Or uses specialized tools
trufflehog git https://github.com/company/repo
gitleaks detect --source .
# Common patterns attackers look for:
# - AWS: AKIA[0-9A-Z]{16}
# - GitHub: ghp_[a-zA-Z0-9]{36}
# - Stripe: sk_live_[a-zA-Z0-9]{24}Exposed in Client-Side Bundle
// VULNERABLE: Using secret in frontend code
const response = await fetch('/api/data', {
headers: {
'Authorization': `Bearer ${process.env.API_SECRET}` // Gets bundled!
}
});
// Anyone can find it in the browser:
// 1. View Page Source
// 2. DevTools > Sources > Search for 'Bearer'
// 3. Check network requestsPrevention Strategies
Use Environment Variables
// .env.local (NEVER commit this file)
DATABASE_URL=postgresql://user:pass@host/db
STRIPE_SECRET_KEY=sk_live_xxx
// .gitignore
.env
.env.local
.env.*.local
// Usage in server-side code only
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// For client-side, use NEXT_PUBLIC_ prefix (public values only!)
// NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xxxPre-commit Hooks
# Install git-secrets
brew install git-secrets
# Add AWS patterns
git secrets --register-aws
# Add custom patterns
git secrets --add 'sk_live_[a-zA-Z0-9]+'
git secrets --add 'password\s*=\s*["'][^"']+["']'
# Install hook in repo
git secrets --install
# Now commits with secrets are blocked automaticallyUse a Secrets Manager
// Fetch secrets at runtime from a secure vault
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManager({ region: 'us-east-1' });
async function getSecret(secretName: string) {
const response = await client.getSecretValue({ SecretId: secretName });
return JSON.parse(response.SecretString!);
}
// Secrets never touch your codebase
const dbCredentials = await getSecret('prod/database');If a Secret is Exposed
- Rotate the secret immediately - don't just remove it from code
- Check logs for unauthorized access during the exposure window
- Use git filter-branch or BFG Repo-Cleaner to remove from history
- Force push and notify all contributors to re-clone
Security Checklist
- Never hardcode secrets in source code
- Add secret patterns to .gitignore (.env, *.pem, credentials.json)
- Use pre-commit hooks to scan for secrets
- Only use NEXT_PUBLIC_ for truly public values
- Rotate secrets regularly and after any potential exposure
Practice Challenges
View allHidden Secrets
A simple login page. But wait... where do those credentials come from?
Env Exposed
A React app with some helpful debug info. Maybe too helpful?
Git Happens
Someone forgot to add .git to their .gitignore... wait, that's not how it works.
Console Log King
Debug logs everywhere. Including some that shouldn't be there.
Source Map Leak
Production build with source maps enabled. See the original code.