# No hardcoded API keys or secrets in source code

- **Pattern:** `ab-000018` (`security-headers.basic-hygiene.no-hardcoded-secrets`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-17
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000018
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000018)

## Why it matters

Hardcoded secrets in source code are the most common high-severity finding in AI-built projects: the model fills in a working API key to make the code run, and the developer ships it without noticing. A Stripe secret key (`sk_live_`) hardcoded in `lib/stripe.ts` gives anyone with repo read access the ability to create charges, refund transactions, or read customer data. A hardcoded JWT secret lets an attacker forge authentication tokens. OWASP A07 (Identification and Authentication Failures) and CWE-798 (Use of Hard-coded Credentials) directly describe this class of defect. PCI-DSS 4.0 Req 8.6.2 explicitly prohibits hard-coded credentials in any system component. Secrets in git history must be rotated even after removal from the code — the history is permanent.

## Severity rationale

Critical because a hardcoded secret key gives anyone with repository read access full API-level access to the associated service, often with no rate limiting or geo restriction.

## Remediation

Move every secret literal to an environment variable reference. The fix is mechanical — find the literal, move it to `.env.local`, and reference it via `process.env`.

```ts
// Before (DANGEROUS — rotate this key immediately):
const stripe = new Stripe('sk_live_abc123...')

// After (SAFE):
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
```

After moving secrets to environment variables: **rotate every exposed key immediately** — assume any secret that ever appeared in your git history has been seen, regardless of whether the repo was private. Use `git log -S 'sk_live_'` to confirm the secret is gone from all commits; if it isn't, you need a history rewrite (`git filter-repo`).

## Detection

- **ID:** `no-hardcoded-secrets`
- **Severity:** `critical`
- **What to look for:** Count all source code files searched and enumerate any hardcoded secret patterns found: API keys, tokens, passwords, connection strings. Only flag **secret/private keys** — not public keys that are intentionally embedded client-side. Look for:
  - Stripe **secret** keys: `sk_live_`, `sk_test_` — flag these. **Do NOT flag** Stripe publishable keys (`pk_live_`, `pk_test_`) — these are designed to be public and are safely embedded in client-side code.
  - GitHub tokens: strings starting with `ghp_`, `gho_`, `ghu_`
  - AWS secret access keys: strings starting with `AKIA` (access key IDs) combined with a secret key value
  - `password = "..."`, `secret = "..."`, or `apiKey = "..."` with literal string values (not environment variable references)
  - Keys containing `_secret_` in any API key pattern
  - Database connection strings with credentials embedded (usernames/passwords in the URL)
  - JWT signing secrets as literal strings
  - Any long alphanumeric string (32+ chars) assigned to a variable named `key`, `secret`, `token`, `password`, or `credential`

  **Important:** Only flag literal string values in source code. References to `process.env.SECRET_KEY` or `import.meta.env.VITE_SECRET` are correct and must NOT be flagged.
- **Before evaluating:** List each directory and file pattern searched (e.g., "Searched `src/`, `lib/`, `app/`, `pages/`, `config/` for patterns: `sk_live_`, `sk_test_`, `ghp_`, `AKIA`, `password =`, `secret =`, `apiKey =`"). For any flagged item, quote the variable name and assignment pattern (not the actual secret value). Example: "Found `const stripeKey = 'sk_test_...'` in `lib/stripe.ts` line 4."
- **Pass criteria:** 0 hardcoded secrets or private keys found in source code after searching at least 3 source directories. All secrets are accessed via environment variables (`process.env.*`, `import.meta.env.*`). Publishable/public keys embedded in client code are acceptable.
- **Fail criteria:** At least 1 hardcoded secret key, private API key, password, or credential found as a literal string in source code files.
- **Do NOT pass when:** Absence of evidence alone is insufficient — actively search all source files, config files, and test fixtures for secret patterns. A cursory scan of only top-level files is not sufficient.
- **Skip (N/A) when:** Never — this check always applies.
- **Detail on fail:** `"Hardcoded Stripe key found in lib/stripe.ts: sk_live_... value assigned directly instead of using process.env"` or `"Database connection string with credentials hardcoded in db/config.ts"`. **Never include the actual secret value in the detail string.**
- **Remediation:** Secrets in source code are the most common security issue in AI-built projects. Move all secrets to environment variables:

  ```ts
  // Before (DANGEROUS):
  const stripe = new Stripe('sk_live_abc123...')

  // After (SAFE):
  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
  ```

  After fixing, rotate any exposed keys immediately — they should be considered compromised if they were ever in your git history. For application-level security analysis including auth flows and authorization, the SaaS Readiness Pack covers this in depth.

## External references

- cwe CWE-798
- cwe CWE-321
- cwe CWE-259
- owasp A07
- pci-dss Req 8.6.2

Taxons: cryptography-and-secrets

HTML version: https://auditbuffet.com/patterns/ab-000018
