Session tokens are cryptographically random and at least 128 bits
Why it matters
Session tokens, password reset links, and email verification codes generated with Math.random() or Date.now() are predictable. CWE-338 (Use of Cryptographically Weak PRNG) and CWE-330 (Use of Insufficiently Random Values) describe how an attacker who knows the approximate generation time can enumerate the token space in seconds, enabling account takeover without ever knowing the victim's password. OWASP A02 (Cryptographic Failures) and NIST 800-53 SC-8 both require cryptographically secure randomness for security tokens. Six-digit numeric email codes have only one million possibilities — brute-forceable in under a minute against a rate-unlimited endpoint.
Severity rationale
Critical because a predictable password-reset or session token lets an attacker take over any account without needing the user's credentials, bypassing authentication entirely.
Remediation
Use crypto.randomBytes or crypto.randomUUID() for every security-sensitive token. Store reset tokens hashed (SHA-256), send the plaintext to the user:
import crypto from 'crypto'
const rawToken = crypto.randomBytes(32).toString('hex') // 256 bits
const storedToken = crypto.createHash('sha256').update(rawToken).digest('hex')
await db.passwordReset.create({
data: { token: storedToken, userId, expiresAt: new Date(Date.now() + 3_600_000) },
})
// Email rawToken to the user, never storedToken
UUID v4 (crypto.randomUUID()) is acceptable for session identifiers — 122 bits of randomness.
Detection
-
ID:
session-token-entropy -
Severity:
critical -
What to look for: Count all session token generation points in the codebase. For each, examine how session tokens, auth tokens, password reset tokens, and email verification tokens are generated. Look for
crypto.randomBytes,crypto.randomUUID,uuid(),nanoid(), or the equivalent secure random generators. Flag uses ofMath.random(),Date.now(), sequential IDs, or predictable patterns. -
Pass criteria: All security-sensitive tokens (sessions, CSRF, password reset, email verification) are generated using a cryptographically secure random number generator and are at least 16 bytes (128 bits) of entropy. UUIDs v4 qualify (122 bits of randomness). Report: "X token generation points found, all Y use cryptographically secure randomness."
-
Fail criteria: Any security token uses
Math.random(),Date.now(), a sequential database ID, or any other non-cryptographic source of randomness. Tokens shorter than 128 bits of entropy also fail. -
Skip (N/A) when: The project uses a managed auth library (NextAuth.js, Clerk, Auth0) that handles all token generation internally.
-
Detail on fail: Name the specific pattern. Example:
"Password reset tokens generated with Math.random().toString(36) — predictable and guessable"or"Email verification tokens are 6-digit numeric codes — only 1M possibilities, brute-forceable" -
Remediation: Use
crypto.randomBytes(Node.js) orcrypto.getRandomValues(Web Crypto API) for all security tokens:import crypto from 'crypto' // Generate a 32-byte (256-bit) hex token const resetToken = crypto.randomBytes(32).toString('hex') // Or use UUID v4 (128-bit, 122 bits of randomness) const sessionToken = crypto.randomUUID() // Store the token (hashed) and associate with user await db.passwordReset.create({ data: { token: crypto.createHash('sha256').update(resetToken).digest('hex'), userId: user.id, expiresAt: new Date(Date.now() + 3600 * 1000), // 1 hour }, }) // Send the unhashed token to the user await sendEmail(user.email, { resetToken })
External references
- cwe · CWE-338 — Use of Cryptographically Weak Pseudo-Random Number Generator
- cwe · CWE-330 — Use of Insufficiently Random Values
- owasp:2021 · A02 — Cryptographic Failures
- nist:rev5 · SC-8 — Transmission Confidentiality and Integrity
Taxons
History
- 2026-04-18·v1.0.0·Initial import from security-hardening·automated