Hardcoded secrets in source files — JWT signing keys, Stripe secret keys, database passwords — are exposed to every developer with repository access, every CI system, every leaked ZIP or public GitHub push. CWE-798 (Hard-coded Credentials) and CWE-312 (Cleartext Storage of Sensitive Information) describe this as a critical class of vulnerability. OWASP A02 (Cryptographic Failures) and NIST 800-53 SC-28 require secrets at rest to be protected. Once a secret is in git history it is effectively public, even after deletion; the only remediation is rotation. A committed Stripe live key can drain accounts within minutes of a repo becoming public.
Critical because committed secrets give anyone with repository read access full control over production services, and git history preservation means the window for damage extends indefinitely.
Move all secrets to environment variables and add .env patterns to .gitignore immediately. If any secret has been committed, rotate it before removing it from history:
git rm --cached .env
echo '.env\n.env.*\n!.env.example' >> .gitignore
Load secrets with a startup assertion:
const secret = process.env.JWT_SECRET
if (!secret) throw new Error('JWT_SECRET is required — set it in your deployment environment')
Provide a .env.example with placeholder values only (never real keys) so developers know which variables are required.
ID: security-hardening.auth-session.secrets-not-committed
Severity: critical
What to look for: Enumerate every secret and credential reference across the codebase (API keys, database passwords, OAuth secrets, signing keys). For each, check .gitignore for .env, .env.*, *.pem, *.key patterns. Search source files for hardcoded API keys, database connection strings with credentials embedded, JWT secrets, private keys, or OAuth client secrets. Look in next.config.*, vite.config.*, config files, and test files — these are common places where secrets accidentally land. Also check package.json scripts for inline credentials.
Pass criteria: .gitignore excludes .env and .env.* files. No API keys, passwords, connection strings with credentials, or private keys appear as string literals in any source file. Environment variables are loaded via process.env or a config module — 0% of secrets should appear in committed files. Report: "X secret references found, 0 committed to version control."
Fail criteria: Any hardcoded secret, API key, database password, JWT signing key, or private key is found in source files. Or .gitignore does not exclude .env files.
Skip (N/A) when: Never — this check always applies regardless of stack.
Do NOT pass when: Secrets appear in .env.example with real values rather than placeholder strings — example files must use dummy values only.
Detail on fail: Describe the finding without including the actual secret. Example: ".env files not listed in .gitignore — any .env files could be committed" or "JWT_SECRET hardcoded as a string literal in lib/auth.ts line 12" or "Database connection string with credentials found in next.config.ts"
Remediation: Move all secrets to environment variables immediately. If any secret has been committed to git history, treat it as compromised and rotate it:
# Check if .env is already tracked by git
git ls-files .env
# Add to .gitignore
echo ".env" >> .gitignore
echo ".env.*" >> .gitignore
echo "!.env.example" >> .gitignore
# Remove from tracking (keeps the file, removes from git)
git rm --cached .env
// Bad: hardcoded secret
const secret = 'my-super-secret-jwt-key-12345'
// Good: environment variable with validation at startup
const secret = process.env.JWT_SECRET
if (!secret) throw new Error('JWT_SECRET environment variable is required')
Provide a .env.example with placeholder values so developers know what variables are needed:
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=<generate with: openssl rand -hex 32>
STRIPE_SECRET_KEY=sk_live_...