Encryption keys stored separately from data in dedicated KMS
Why it matters
CWE-321 (use of hard-coded cryptographic key) describes the failure where the key and the data it protects are co-located — making the key trivially recoverable from anyone who can read the codebase, a git history export, or an environment variable committed to source control. NIST SC-12 requires that cryptographic keys be generated, distributed, stored, and destroyed through a formal key management process. PCI-DSS 4.0 Req-3.7 requires split knowledge and dual control for key custodianship. An encryption key stored next to the data it encrypts — whether in a .env committed to git, config/keys.json, or hardcoded in source — means any code read gives an attacker both the ciphertext and the key to decrypt it. OWASP 2021 A02 explicitly cites key co-location as a cryptographic failure root cause.
Severity rationale
High because a key stored alongside encrypted data completely defeats encryption — any attacker with codebase read access can decrypt all protected records without brute force.
Remediation
Migrate encryption key loading to a dedicated KMS. AWS KMS, GCP Cloud KMS, and Azure Key Vault all provide key material that never leaves the service — your application receives ciphertext, not the raw key. For AWS:
// src/lib/kms.ts
import { KMSClient, EncryptCommand, DecryptCommand } from '@aws-sdk/client-kms';
const client = new KMSClient({ region: process.env.AWS_REGION });
export async function kmsEncrypt(plaintext: string): Promise<string> {
const cmd = new EncryptCommand({
KeyId: process.env.AWS_KMS_KEY_ID!,
Plaintext: Buffer.from(plaintext),
});
const result = await client.send(cmd);
return Buffer.from(result.CiphertextBlob!).toString('base64');
}
export async function kmsDecrypt(ciphertext: string): Promise<string> {
const cmd = new DecryptCommand({
CiphertextBlob: Buffer.from(ciphertext, 'base64'),
});
const result = await client.send(cmd);
return Buffer.from(result.Plaintext!).toString('utf8');
}
Remove all key material from the codebase. If a key was ever committed to git, rotate it immediately — git history is permanent.
Detection
- ID:
keys-in-kms - Severity:
high - What to look for: Count all encryption key storage locations (KMS, environment variables, config files, hardcoded). Quote the actual KMS service or key storage mechanism found. Enumerate all code paths that load encryption keys and classify each as "KMS-backed" or "local." Keys stored in .env files committed to git do not count as pass — do not pass if any key is stored in the codebase.
- Pass criteria: At least 1 dedicated KMS (cloud provider or self-hosted HSM/Vault) is used. Count all key-loading code paths — at least 90% must fetch from KMS at runtime. Report the count even on pass (e.g., "AWS KMS configured, 3 of 3 key-loading paths use KMS, 0 keys in codebase").
- Fail criteria: Encryption keys stored in application codebase, .env files committed to git, database alongside encrypted data, or in application config files — any 1 instance fails.
- Skip (N/A) when: Project uses encryption libraries but manages keys through cloud provider defaults (AWS Secrets Manager for non-KMS key storage) — cite the actual service found.
- Detail on fail:
"0 KMS integrations found — encryption key loaded from config/keys.json in codebase"or"1 of 3 key-loading paths reads from .env file committed to git" - Cross-reference: Check
finserv-encryption.key-management.key-rotation-annualfor rotation policy, andfinserv-encryption.pci-alignment.environment-specific-keysfor per-environment key separation. - Remediation:
- Adopt a Key Management Service:
// AWS KMS example import AWS from 'aws-sdk'; const kms = new AWS.KMS(); const encryptData = async (plaintext: string) => { const result = await kms.encrypt({ KeyId: process.env.AWS_KMS_KEY_ID!, Plaintext: plaintext, }).promise(); return result.CiphertextBlob.toString('base64'); }; - Or use a managed service:
// Example: Using Supabase Vault const { data, error } = await supabase .from('secrets') .update({ value: supabase.vault.encrypt('key_data') }) .eq('name', 'encryption_key'); - Remove keys from codebase and .env files committed to git:
git rm .env # Remove committed env file echo ".env" >> .gitignore
- Adopt a Key Management Service:
External references
- cwe · CWE-321 — Use of Hard-coded Cryptographic Key
- owasp:2021 · A02 — Cryptographic Failures
- nist:rev5 · SC-12 — Cryptographic Key Establishment and Management
- pci-dss:4.0 · Req-3.7 — Key management policies and procedures for cryptographic keys
Taxons
History
- 2026-04-18·v1.0.0·Initial import from finserv-encryption·automated