Encryption keys are read from env, not hardcoded
Why it matters
A hardcoded AES key like 'this-is-a-32-character-secret!!' passed to crypto.createCipheriv() is committed to source control and therefore permanently exposed to every developer who has ever had repo access, every CI system, every build log, and every git history tool. CWE-321 (Use of Hard-coded Cryptographic Key) and OWASP A02 (Cryptographic Failures) describe the consequence: if the key is compromised — and it is, the moment it was committed — every record encrypted with that key is decryptable by anyone with the history.
Severity rationale
Medium because hardcoded crypto keys are permanently exposed in source history, but exploitation requires access to the codebase or ciphertext — not just network access.
Remediation
Read the key from an environment variable and validate it at startup with a Zod schema to fail fast if it's missing or malformed.
// Bad: key is a string literal in source
const cipher = crypto.createCipheriv('aes-256-cbc', 'this-is-a-32-character-secret!!', iv)
// Good: key from env, validated at startup
import { z } from 'zod'
const Env = z.object({ ENCRYPTION_KEY: z.string().length(64) }) // 32 bytes = 64 hex chars
const env = Env.parse(process.env)
const key = Buffer.from(env.ENCRYPTION_KEY, 'hex')
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv)
Generate a secure key with node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" and store it in .env.local (never commit it). Rotate the key and re-encrypt all records if it was previously hardcoded.
Detection
-
ID:
crypto-keys-from-env -
Severity:
medium -
What to look for: Walk source files for calls to
crypto.createCipheriv(,crypto.createDecipheriv(,crypto.createHmac(,crypto.scryptSync(,crypto.createSign(,crypto.createVerify(,nacl.secretbox(,nacl.box.keyPair.fromSecretKey(,aes.encrypt(,aes.decrypt(,Cipher((CryptoJS). For each call site, before evaluating, extract and quote the key argument expression. Count all call sites where the key argument is a string literal of length 16/24/32/64 (likely hardcoded key) versus those where the key referencesprocess.env.X,env.X, orBuffer.from(process.env.X, 'hex'). -
Pass criteria: 100% of crypto calls source their key from environment variables. Report: "X crypto call sites, Y use env-based keys, 0 hardcoded."
-
Fail criteria: At least 1 crypto call passes a string literal as the key argument.
-
Skip (N/A) when: No
crypto.createCipheriv/nacl/crypto-jscalls anywhere in source. -
Detail on fail:
"1 hardcoded crypto key: src/lib/encrypt.ts line 8 calls crypto.createCipheriv('aes-256-cbc', 'this-is-a-32-character-secret!!', iv) — key is a literal, not from env" -
Remediation: Hardcoded crypto keys are committed to source control and visible in every backup, log, and stack trace. Always read keys from env:
// Bad: hardcoded key const cipher = crypto.createCipheriv('aes-256-cbc', 'this-is-a-32-character-secret!!', iv) // Good: env-based key const key = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex') const cipher = crypto.createCipheriv('aes-256-cbc', key, iv)Use a typed env schema (Zod) to fail fast at startup if
ENCRYPTION_KEYis missing.
External references
- cwe · CWE-321 — Use of Hard-coded Cryptographic Key
- cwe · CWE-798 — Use of Hard-coded Credentials
- owasp:2021 · A02 — Cryptographic Failures
- owasp:2021 · A07 — Identification and Authentication Failures
Taxons
History
- 2026-04-18·v1.0.0·Initial import from ai-slop-security-theater·automated