Web Crypto API
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list eslint and semgrep as the tools. Misuse of Math.random() for crypto or use of third-party JS crypto libraries instead of Web Crypto can be caught by semgrep rules or ESLint security plugins, but these are not default linter rules — they require specialist configuration. Not d3 because the pattern isn't caught by a default linter setup, and not d7 because automated tooling (semgrep) can reliably surface the common patterns.
Closest to 'simple parameterised fix' (e3). The quick_fix indicates replacing Math.random() with crypto.getRandomValues() or swapping a third-party library for crypto.subtle calls. This is typically a targeted replacement within one component or module — not a one-liner (requires understanding the API and adapting call sites), but not a cross-cutting refactor either. Hardcoded IV or AES-CBC without MAC mistakes may require a few co-located changes, keeping this at e3.
Closest to 'localised tax' (b3). The applies_to context is web only, and the choice of how to implement client-side crypto is generally localised to a crypto utility module or specific feature (e.g. e2e encryption). It doesn't impose a codebase-wide tax — most application code is unaffected. However, if non-extractable keys and IndexedDB are used, it does impose some ongoing complexity in that component, so b3 is appropriate rather than b1.
Closest to 'serious trap' (t7). The misconception is explicit: developers believe Math.random() and crypto.getRandomValues() are equivalent for security purposes. This directly contradicts how randomness is conceptually understood — 'random is random' — when in fact Math.random() is deterministic and predictable. Additional traps include assuming AES-CBC is safe without a MAC, and reusing IVs. These are well-documented gotchas that contradict intuition from general programming contexts, placing this at t7.
Also Known As
TL;DR
Explanation
Web Crypto API (crypto.subtle): digests (SHA-256/384/512), key generation (AES-GCM, RSA-OAEP, ECDSA), encryption/decryption (AES-GCM), signing/verification (RSA-PSS, ECDSA), key derivation (PBKDF2, HKDF), key import/export. All operations are async (Promise-based) and work with ArrayBuffer. crypto.getRandomValues() provides cryptographically secure random bytes synchronously. CryptoKey objects are non-extractable by default — prevents key material from leaking to JavaScript variables.
Common Misconception
Why It Matters
Common Mistakes
- Math.random() for tokens, IDs, or any security-sensitive randomness
- Storing private keys in localStorage — use non-extractable CryptoKey with IndexedDB
- Not using authenticated encryption (AES-GCM) — AES-CBC without MAC allows ciphertext tampering
- Hardcoded IVs — always generate a unique random IV per encryption operation
Code Examples
// Math.random for crypto — predictable and insecure:
const token = Math.random().toString(36).substring(2);
// Token can be predicted with knowledge of the PRNG state
// Cryptographically secure random token:
const bytes = crypto.getRandomValues(new Uint8Array(32));
const token = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
// AES-GCM authenticated encryption:
async function encrypt(plaintext, key) {
const iv = crypto.getRandomValues(new Uint8Array(12)); // Unique per message
const encoded = new TextEncoder().encode(plaintext);
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key, // CryptoKey — non-extractable
encoded
);
return { iv, ciphertext }; // Store IV alongside ciphertext
}