Skip to content

ALL-SECU0003 · unseeded_randomness_for_secrets

Count the unguessable values an ordinary random generator produced.

This is a deterministic rule for all languages. Read its implementation.

Read every binding whose name promises a value nobody may predict, such as a token, a nonce, a session id, or an api key, then report the calls beneath it that reach a general purpose pseudo random generator. random, Math.random, rand, srand, and thread_rng all run a fast deterministic sequence that an observer recovers after collecting a handful of outputs, so a token minted from one is guessable by anyone patient enough to collect them. The cost arrives as account takeover rather than as a crash a test would have caught, which is why no amount of later testing finds it.

Each finding names the declaration, the bound name, and the generator the call reaches. The value is how many predictable draws land under a name that promised secrecy.

Randomness that guards nothing is fine, so a retry delay, a sampled batch, or a test fixture is never reported, because the name never claimed the value had to be unguessable. A generator built for secrets, such as secrets, os.urandom, crypto.getRandomValues, or SecureRandom, is the answer this rule asks for and stays welcome even under a secret name. A bare key is a map key far more often than a credential, so it takes a qualifier to count. A project with its own wrapper around an ordinary generator names it through also_predictable.

const sessionToken = Math.random().toString(36).slice(2)
const sessionToken = crypto.randomUUID()
  • Generalizes Ruff S311 suspicious-non-cryptographic-random-usage. Open reference
  • Cites “Common Weakness Enumeration”, CWE-338, weak pseudo random number generation. Open reference
  • Cites “The Python Standard Library”, secrets, secure random numbers. Open reference