Skip to content

ALL-SECU0002 · weak_hashing_primitive

Count the calls that reach a hash primitive the world already knows how to break.

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

Read every call a declaration states and report one that reaches MD5, SHA-1, or another digest with a published collision. Most languages carry the primitive in the callee name and a factory carries it in the literal handed to the call, so both are read and one rule answers for hashlib.md5, MD5.Create, Md5::new, and createHash('md5'). A broken digest is expensive the moment it ships, because a collision lets someone swap the content behind a signature that still verifies, and every artifact already signed with it has to be signed again by hand.

Each finding names the declaration, the call, and the line. The value is how many calls reach a broken primitive. A nested call keeps its own node, so outer(md5(data)) is read once.

A digest that guards nothing, such as a cache key or a shard bucket, costs nothing when it collides, so a call that states it is not used for security is left alone. A project that wraps its own name around a broken primitive names that wrapper through also_broken, since no list of spellings is ever finished. The factories setting names call spellings whose literal arguments carry the primitive rather than the callee itself.

signature = hashlib.md5(payload).hexdigest()
signature = hashlib.sha256(payload).hexdigest()
  • Generalizes Ruff S324 hashlib-insecure-hash-function. Open reference
  • Cites “Common Weakness Enumeration”, CWE-327, use of a broken or risky cryptographic algorithm. Open reference
  • Cites “The First Collision for Full SHA-1”. Open reference