# API keys and secrets are loaded exclusively from environment variables

- **Pattern:** `ab-001157` (`ecommerce-payment-security.payment-integration.keys-from-env`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001157
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001157)

## Why it matters

A hardcoded Stripe secret key (`sk_live_*`) in source code becomes a permanent fixture of your git history. Even if you delete the line in a later commit, the key is recoverable by anyone with read access to the repository — including all historical collaborators, any connected CI/CD system, and anyone who ever cloned the repo. CWE-798 (Use of Hard-coded Credentials) and PCI-DSS 4.0 Req 3.6 (protection of cryptographic keys) both treat this as a critical violation. OWASP A07 (Identification and Authentication Failures) flags hardcoded credentials as a root-cause pattern for account takeover. The only safe recovery from a hardcoded secret is immediate key rotation — the exposed key must be treated as permanently compromised.

## Severity rationale

High because a hardcoded key is permanently embedded in git history, meaning rotation is required the moment it is detected and any delay leaves the Stripe account accessible to repository readers.

## Remediation

Load all payment credentials exclusively from environment variables and validate their presence at startup.

```ts
// lib/stripe.ts
import Stripe from 'stripe'

if (!process.env.STRIPE_SECRET_KEY) {
  throw new Error('STRIPE_SECRET_KEY is not set')
}

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
  apiVersion: '2024-12-18.acacia',
})
```

Check `.env.example` into version control with placeholder values only:

```
STRIPE_SECRET_KEY=sk_test_replace_me
STRIPE_PUBLISHABLE_KEY=pk_test_replace_me
STRIPE_WEBHOOK_SECRET=whsec_replace_me
```

If any key was ever hardcoded and committed, rotate it in the Stripe Dashboard immediately — git history is not erasable without a full rewrite.

## Detection

- **ID:** `keys-from-env`
- **Severity:** `high`
- **What to look for:** Count every source file that references payment provider key patterns (`sk_live_`, `sk_test_`, `pk_live_`, `pk_test_`, `whsec_`, `rk_live_`). For each match, classify it as environment-loaded (`process.env.*`) or hardcoded (string literal). Search config files, service initializers, utility modules, JSON config files, database seed scripts, and test fixtures. Verify that `.env` or `.env.local` is listed in `.gitignore`.
- **Before evaluating:** Quote the variable name and assignment pattern (not the actual secret value) for each key reference found. Example: "Found `const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)` in `lib/stripe.ts` line 5."
- **Pass criteria:** All payment API keys and signing secrets are loaded exclusively via `process.env.*` (or framework equivalent). No more than 0 hardcoded key literals should exist. No key material appears as a string literal anywhere in source files. `.env` files are gitignored.
- **Fail criteria:** Any payment key is hardcoded as a string literal in any source file, regardless of whether that file is client-side or server-side.
- **Skip (N/A) when:** The project uses a managed payment service where credentials are injected by the platform (e.g., Shopify Payments built-in), and no manual key configuration exists.
- **Detail on fail:** Name the specific file and key type. Example: `"Stripe secret key hardcoded as string literal in lib/stripe.ts — rotate this key immediately as it may have been committed to version control"`
- **Cross-reference:** The Environment Security audit covers comprehensive credential scanning and `.env` hygiene across all file types, not limited to payment keys.
- **Remediation:** Load all credentials exclusively from environment variables:

  ```ts
  // lib/stripe.ts — CORRECT
  import Stripe from 'stripe'

  if (!process.env.STRIPE_SECRET_KEY) {
    throw new Error('STRIPE_SECRET_KEY environment variable is not set')
  }

  export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
    apiVersion: '2024-12-18.acacia',
  })
  ```

  Add the following to `.env.example` (checked into version control with no actual values):

  ```
  STRIPE_SECRET_KEY=sk_test_your_key_here
  STRIPE_PUBLISHABLE_KEY=pk_test_your_key_here
  STRIPE_WEBHOOK_SECRET=whsec_your_secret_here
  ```

  If a key was ever hardcoded and committed, rotate it immediately in your provider's dashboard — git history is permanent.

## External references

- cwe CWE-321
- cwe CWE-798
- owasp A07
- pci-dss Req 3.6

Taxons: cryptography-and-secrets

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