Why AI tools produce this: AI coding tools produce this regularly, typically when prompted for a shortcut or a quick fix.
Why this matters
JavaScript's equality operators short-circuit on the first differing byte, which leaks the matching prefix length over the wire, a classic timing-attack vector.
Use crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)) (both buffers must be the same length, so hash first if needed). For password verification, use argon2.verify, bcrypt.compare, or scrypt. They handle constant-time comparison for you.
VULNERABLE
vulnerable.ts
declare const hashedPassword: string;declare const submittedToken: string;declare const expectedHmac: string;declare const apiKey: string;export function loginBad(input: string) { // ruleid: auth.flow.timing-unsafe-compare return input === hashedPassword;}export function checkApiKey(provided: string) { // ruleid: auth.flow.timing-unsafe-compare return provided === apiKey;}export function verifyHmac(actualHmac: string) { // ruleid: auth.flow.timing-unsafe-compare return expectedHmac !== actualHmac;}export function loginLoose(input: string) { // ruleid: auth.flow.timing-unsafe-compare -- loose equality is just as unsafe return input == hashedPassword;}
SAFE
safe.ts
import { timingSafeEqual } from 'node:crypto';import argon2 from 'argon2';// ok: auth.flow.timing-unsafe-compareexport function loginGood(input: string, stored: string) { return argon2.verify(stored, input);}// ok: auth.flow.timing-unsafe-compareexport function verifyHmacSafe(expected: Buffer, actual: Buffer) { return expected.length === actual.length && timingSafeEqual(expected, actual);}// ok: auth.flow.timing-unsafe-compare -- comparing non-secret values is fineexport function compareUsernames(a: string, b: string) { return a === b;}// ok: auth.flow.timing-unsafe-compare -- loose null/presence checks are not comparisonsexport function presenceChecks(token?: string) { if (token == null) return false; if (typeof token != 'string') return false; return token.length != 0;}// ok: auth.flow.timing-unsafe-compare -- comparing a secret-named value to a// string literal is not a timing target (the literal is public in source).export function demoPassword(password: string) { return password !== 'password';}// ok: auth.flow.timing-unsafe-compare -- boolean-literal / feature-flag checksexport function featureFlag(opts: { idToken?: boolean }) { if (opts.idToken === false) return false; return opts.idToken !== true;}
Suppressing this rule
If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.