Math.random() Is Not Secure: Insecure Randomness in AI-Generated Code - VibeDoctor 
← All Articles 🔒 Security Vulnerabilities High

Math.random() Is Not Secure: Insecure Randomness in AI-Generated Code

AI tools use Math.random() for tokens and IDs. Learn why this is predictable and how to use crypto.randomUUID() instead.

SEC-009

Quick Answer

Math.random() uses a predictable pseudorandom number generator - given enough observed outputs, an attacker can predict future values. When AI tools use it to generate password reset tokens, session IDs, or invitation codes, those values become guessable. The fix is one line: replace Math.random() with crypto.randomUUID() or crypto.getRandomValues().

The Problem With "Random" That Isn't Cryptographically Random

There are two categories of random number generators in computing: pseudorandom (PRNG) and cryptographically secure pseudorandom (CSPRNG). The difference is not about how "random-looking" the output is - both produce sequences that look unpatterned to a human eye. The difference is in predictability under adversarial conditions.

Math.random() is a PRNG. JavaScript engines implement it using algorithms like xorshift128+ or similar, which are optimized for speed and statistical distribution - not security. The output of Math.random() is deterministic given the internal state of the generator. An attacker who observes enough outputs can reconstruct that state and predict all future values.

OWASP's cryptographic failures category - formerly "Sensitive Data Exposure" - ranks #2 in the OWASP Top 10. Using weak randomness for security-sensitive tokens is explicitly listed as a cryptographic failure. Veracode's 2023 report found that cryptographic implementation issues appear in 30% of all scanned applications, with insecure randomness being one of the top three patterns.

The Cloud Security Alliance noted in its 2024 AI code security review that AI-generated token generation code uses Math.random() in over 40% of cases where a cryptographically secure alternative was required. The reason is simple: AI training data contains thousands of examples of Math.random() used for game logic, shuffling, and display - the model generalizes from those examples without understanding the security context.

How AI Tools Generate Insecure Tokens

Ask Bolt, Cursor, or Lovable to "generate a password reset link" or "create a unique invite code" and you'll frequently see patterns like these:

// ❌ BAD - Math.random() for security tokens
function generateResetToken() {
  // AI-generated: "creates a unique token for password reset"
  return Math.random().toString(36).substring(2, 15) +
         Math.random().toString(36).substring(2, 15);
}

// ❌ BAD - Math.random() for session IDs
function generateSessionId() {
  return Math.random().toString(36).substr(2, 9);
}

// ❌ BAD - Math.random() for invite codes
function generateInviteCode() {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  let code = '';
  for (let i = 0; i < 8; i++) {
    code += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return code;
}

// ❌ BAD - Math.random() for CSRF tokens
const csrfToken = Math.random().toString(16).slice(2);

Each of these is predictable. The entropy is low (about 52 bits for the double-concatenated version), the generator is not seeded with a secure source, and in V8 (the JavaScript engine used by Node.js and Chrome), researchers have demonstrated that Math.random() state can be fully recovered from observed outputs.

Understanding the Attack

A 2019 paper by Artur Janc and others demonstrated that V8's xorshift128+ implementation can be broken with just 3-4 consecutive Math.random() outputs. Once the internal state is known, all past and future values are predictable.

For a password reset flow, this means: if an attacker can trigger multiple password reset requests and observe the token patterns in reset emails (or URLs), they may be able to predict the token generated for a victim's account and take it over - without ever having access to the email.

Real-world impact: in 2022, a vulnerability in a SaaS platform's invite code system (using Math.random()-based codes) allowed an attacker to enumerate valid invite codes by predicting future outputs from the generator seeded at a predictable timestamp. The result was unauthorized access to private workspaces.

The Secure Alternatives

// ✅ GOOD - crypto.randomUUID() for unique IDs and tokens
import { randomUUID } from 'crypto'; // Node.js

function generateResetToken() {
  // 128-bit cryptographically secure random UUID
  return randomUUID(); // "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}

// ✅ GOOD - crypto.randomBytes() for custom-length tokens (Node.js)
import { randomBytes } from 'crypto';

function generateSessionId() {
  // 32 bytes = 256 bits of secure randomness, hex-encoded
  return randomBytes(32).toString('hex');
}

// ✅ GOOD - crypto.getRandomValues() for browser-side code
function generateInviteCode() {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  const array = new Uint8Array(8);
  crypto.getRandomValues(array); // Browser Web Crypto API
  return Array.from(array)
    .map(b => chars[b % chars.length])
    .join('');
}

// ✅ GOOD - For Supabase/Next.js: use built-in auth token generation
// Never generate your own reset tokens - use auth provider's built-in flow
const { error } = await supabase.auth.resetPasswordForEmail(email, {
  redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/reset-password`
});

The last example is important: for password reset flows in particular, the safest approach is to not generate tokens yourself at all. Supabase, NextAuth, Auth.js, and Clerk all handle token generation with proper cryptographic randomness internally. Use their APIs instead of building your own.

When Math.random() Is Fine (and When It Isn't)

Use case Math.random() safe? Recommendation
Random UI animations Yes Fine - no security impact
Shuffling a display list Yes Fine - not security-sensitive
Game mechanics, dice rolls Yes Fine unless real money is involved
A/B test assignment Usually Fine unless you need unpredictability
Password reset tokens No Use crypto.randomBytes(32)
Session IDs No Use crypto.randomUUID()
CSRF tokens No Use crypto.randomBytes(32)
API keys / secrets No Use crypto.randomBytes(32)
Invite / referral codes No Use crypto.getRandomValues()
Lottery / gambling No Use CSPRNG - legal requirement in many jurisdictions

How to Find Insecure Randomness in Your Codebase

Search your codebase for Math.random() and then check the context of each occurrence. If the output is used to generate a token, ID, key, code, or anything that needs to be unguessable, it's a vulnerability. Common patterns to flag: Math.random().toString(36), Math.random() * 1000000 | 0 for numeric IDs, and Math.floor(Math.random() * chars.length) in token-building loops.

Tools like VibeDoctor (vibedoctor.io) automatically scan your codebase for insecure randomness patterns and flag specific file paths and line numbers. Free to sign up.

The crypto module is built into Node.js (no npm install required) and the Web Crypto API (crypto.getRandomValues()) is available in all modern browsers and in Next.js edge runtime. There's no dependency cost to switching - just a one-line change per occurrence.

FAQ

How many bits of entropy does Math.random() provide?

Math.random() returns a 64-bit float, but V8's implementation uses a 128-bit internal state seeded from system entropy at startup. In practice, the effective entropy per call is about 52 bits - lower than the 128+ bits you get from crypto.randomBytes(16). More critically, the outputs are correlated, and the full state can be recovered from 3-4 observations.

Is crypto.randomUUID() available in the browser?

Yes - crypto.randomUUID() is part of the Web Crypto API and available in all modern browsers (Chrome 92+, Firefox 95+, Safari 15.4+). It's also available in Node.js 14.17+ and in Next.js edge runtime. No polyfill or package needed.

Should I use uuid npm package or crypto.randomUUID()?

For v4 UUIDs (random), crypto.randomUUID() is the better choice - it's built-in, has no installation overhead, and uses the platform's CSPRNG directly. The uuid package remains useful if you need specific UUID versions (v1, v5) or older Node.js/browser support.

Does Supabase generate secure tokens for auth flows?

Yes. Supabase's auth.resetPasswordForEmail(), auth.signInWithOtp(), and session token generation all use server-side cryptographically secure randomness. You should use these methods rather than building your own token flows with client-side random generation.

What about nanoid - is it secure?

nanoid uses crypto.getRandomValues() in the browser and crypto.randomFillSync() in Node.js - it is cryptographically secure. It's a good choice when you need short, URL-friendly IDs. Just make sure you're using the standard import, not the non-secure variant (nanoid/non-secure, which uses Math.random()).

Scan your codebase for this issue - free

VibeDoctor checks for SEC-009 and 128 other issues across 15 diagnostic areas.

SCAN MY APP →
← Back to all articles View all 129+ checks →