Mesrai
Back to blog
// essaySecurity

Insecure Random in Token Generation: Math.random() → crypto

Real PR catch: `Math.random` for reset tokens — V8's xorshift state is recoverable. Migration to `crypto.randomBytes`. CWE-338.

Mesrai TeamJuly 24, 20268 min read

A password-reset endpoint. Generates a token, emails it. The token generator used `Math.random()` because it was the first thing the author reached for. Mesrai flagged it.

The vulnerable diff

tsauth/reset-token.ts
// auth/reset-token.ts
export function generateResetToken(): string {
  // BUG: Math.random is a non-cryptographic PRNG.
  // V8 uses xorshift128+; state recovers from 5 outputs.
  return Math.random().toString(36).slice(2);
}

What is wrong

JavaScript's `Math.random` is implemented in V8 with the xorshift128+ algorithm — fast, well-distributed, completely non-cryptographic. Given a handful of consecutive outputs, the internal state of xorshift128+ can be recovered in under a second on a laptop, and from that state every past and future output is predictable. CWE-338 covers the misuse of weak PRNGs for security-sensitive token generation. The right primitive in Node is `crypto.randomBytes`, backed by the OS entropy pool.

The attack

Attack path: the application exposes any endpoint that returns a `Math.random` output to the user — a request id in a response header, a tracking pixel id, a UUIDv4 generated with `Math.random`. The attacker collects a few of those, recovers the state, then triggers a password reset on the victim's account and predicts the reset token before the email even arrives.

textexploit
1. Attacker collects 5 consecutive Math.random outputs from any
   public endpoint (request ids, UUIDv4, tracking ids).

2. Runs a published xorshift128+ state-recovery script
   (~1 second on a laptop). Now knows the internal PRNG state.

3. Triggers password reset on victim's account. Predicts the
   token. Resets the password before the victim even sees
   the email.

Public research has shipped working state-recovery code for V8 since 2014. This is not theoretical.

Mesrai's review comment

textreview-comment.txt
mesraipilot · Bot · reviewed 30 sec ago

[mesrai] [code-review] [Security] [CWE-338] [high]

`Math.random()` is V8's xorshift128+ PRNG. Not cryptographic. From any
sequence of ~5 consecutive outputs an attacker can recover the
internal state and predict every future output.

For a security-sensitive token use `crypto.randomBytes`:

  import crypto from "crypto";
  const token = crypto.randomBytes(32).toString("base64url");

The same rule applies to:

  - session ids
  - password reset tokens
  - CSRF tokens
  - API keys
  - shareable invite links
  - magic login tokens

Never `Math.random`, never the `uuid` v4 from packages backed by
`Math.random`. Use `crypto.randomUUID()` (Node 14.17+) which is backed
by the OS RNG.

Reference: OWASP Cheat Sheet on Randomness, CWE-338

The fix

tsauth/reset-token.ts (fixed)
// auth/reset-token.ts — fixed
import crypto from "crypto";

export function generateResetToken(): string {
  return crypto.randomBytes(32).toString("base64url");
}

32 bytes from the OS entropy pool, base64url-encoded for safe URL transport. Unpredictable, sufficient entropy for any security purpose, one line. For UUID-shaped IDs the equivalent is `crypto.randomUUID()` — Node's built-in cryptographically-secure UUIDv4 generator since 14.17.

Why human review missed it

`Math.random` reads as 'a random number' to most engineers — the cryptographic-vs-statistical distinction is rarely part of day-to-day JS. The bug is also invisible at runtime: the tokens look random in logs, never collide in normal use. The vulnerability lives in the predictability under adversarial conditions. Mesrai's rule pack catches `Math.random` used near any of the canonical security-sensitive variable names.

Related rules + further reading

Mesrai rule pack: security/no-math-random-for-tokens — flags `Math.random` in token/session/csrf/reset variable contexts.

OWASP Cheat Sheet: Insecure Randomness.

CWE-338 — Use of Cryptographically Weak Pseudo-Random Number Generator.

Takeaway

Every security-sensitive token must come from `crypto.randomBytes` or `crypto.randomUUID`. `Math.random` is for animations, not auth.

// try

See it on your next PR.

Free for individuals. Install in two minutes. Mesrai reviews every commit.