Back to Learning Center
criticalOWASP A02:2021CWE-200CWE-312

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

hardcoded-secrets.js
// 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 env

Committed to Git History

Even if you remove a secret from code, it remains in git history forever:

git-secrets.sh
# 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

client-exposure.js
// 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 requests

Prevention Strategies

Use Environment Variables

env-variables.ts
// .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_xxx

Pre-commit Hooks

pre-commit-setup.sh
# 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 automatically

Use a Secrets Manager

secrets-manager.ts
// 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

  1. Rotate the secret immediately - don't just remove it from code
  2. Check logs for unauthorized access during the exposure window
  3. Use git filter-branch or BFG Repo-Cleaner to remove from history
  4. 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 all

Related Articles

View all