All sensitive financial data encrypted at rest using AES-256+
Why it matters
Unencrypted financial data at rest — account numbers, SSNs, card tokens, transaction history — is a direct path from database breach to identity theft and fraud. CWE-311 (missing encryption) and CWE-326 (inadequate key length) are both in play when a project stores plaintext or uses AES-128: attackers who exfiltrate the database file get immediately usable cardholder data. NIST SP 800-111 and PCI-DSS 4.0 Req-3.5 require AES-256 or stronger because shorter keys are vulnerable to brute-force as compute costs fall. A single exposed plaintext column is enough to trigger breach notification obligations and PCI fines.
Severity rationale
Critical because a single unencrypted sensitive column converts a database read vulnerability into direct cardholder data exposure with no further attacker effort required.
Remediation
Switch to AES-256-GCM for all sensitive fields in src/lib/encryption.ts or src/utils/crypto.ts. Prefer authenticated encryption (GCM mode) over CBC to also detect tampering. Use the Node.js built-in crypto module — do not add a third-party wrapper for this:
import crypto from 'node:crypto';
const ALGO = 'aes-256-gcm';
export function encryptField(plaintext: string, key: Buffer): string {
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv(ALGO, key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
return [iv.toString('hex'), tag.toString('hex'), encrypted.toString('hex')].join(':');
}
For database-level coverage, enable TDE on your provider (AWS RDS, Supabase) — that covers all columns, including ones you haven't explicitly wrapped. Both approaches together satisfy NIST SC-28 and PCI-DSS 4.0 Req-3.5.
Detection
- ID:
aes256-encryption - Severity:
critical - What to look for: Enumerate all sensitive financial data columns (account numbers, transaction history, payment credentials, PII) and count every column that is encrypted vs. unencrypted. Quote the actual encryption algorithm found (e.g.,
aes-256-gcm,aes-256-cbc). Check for transparent database encryption (TDE) or application-level encryption. A key size under 256 bits does not count as pass — do not pass if any sensitive field uses weaker than AES-256. - Pass criteria: At least 90% of sensitive financial data columns are encrypted with AES-256 or stronger, either at database level (TDE) or application level. Count all sensitive columns — report the ratio even on pass (e.g., "8 of 8 sensitive columns encrypted with AES-256-GCM"). Quote the actual algorithm and key length used.
- Fail criteria: Any sensitive financial data stored unencrypted, or encryption is weaker than AES-256 (e.g., AES-128, DES, basic obfuscation).
- Skip (N/A) when: Project is API-only with no data storage, or exclusively delegates storage to third-party payment processor (e.g., Stripe-only — cite the actual processor found, no local customer data).
- Detail on fail: Specify which fields lack encryption. Example:
"3 of 5 sensitive columns unencrypted — account_number, ssn, and card_token stored plaintext in customer_data table". - Cross-reference: Check
finserv-encryption.data-at-rest.tde-or-column-encryptionfor database-level encryption details, andfinserv-encryption.key-management.nist-algorithm-justificationfor algorithm selection. - Remediation: Adopt AES-256 encryption for sensitive fields (in
src/lib/encryption.tsorsrc/utils/crypto.ts). For Node.js, use thecryptomodule. Choose one of two approaches:- Database-Level (Recommended): Enable Transparent Data Encryption (TDE) in PostgreSQL, MySQL, or managed cloud databases (AWS RDS, Supabase, etc.).
- Application-Level: Encrypt sensitive fields before storing:
import crypto from 'crypto'; const cipher = crypto.createCipher('aes-256-cbc', encryptionKey); let encrypted = cipher.update(accountNumber, 'utf8', 'hex'); encrypted += cipher.final('hex'); await db.insert({ account_number: encrypted });
External references
- cwe · CWE-311 — Missing Encryption of Sensitive Data
- cwe · CWE-326 — Inadequate Encryption Strength
- owasp:2021 · A02 — Cryptographic Failures
- nist:rev5 · SC-28 — Protection of Information at Rest
- pci-dss:4.0 · Req-3.5 — Primary Account Number protected with strong cryptography
- nist:final · SP-800-111 — Guide to Storage Encryption Technologies for End User Devices
Taxons
History
- 2026-04-18·v1.0.0·Initial import from finserv-encryption·automated