Comparing a secret, an API key, a token, a signature, with a normal equality check returns as soon as the first character differs. Measured across many requests, that tiny timing difference lets an attacker recover the secret one character at a time. Use a constant-time comparison that always takes the same amount of time.
Why it's a problem
A regular comparison is an optimization: it stops at the first mismatch. That means a guess with a correct first character takes fractionally longer to reject than one that is wrong immediately. An attacker who can measure response times uses that signal to brute-force the secret far faster than blind guessing, character by character.
The pattern
if (providedToken === process.env.API_SECRET) {
// authorized
}The fix
import { timingSafeEqual } from "node:crypto";
const a = Buffer.from(providedToken);
const b = Buffer.from(process.env.API_SECRET);
if (a.length === b.length && timingSafeEqual(a, b)) {
// authorized
}Why AI tools write this
=== is the obvious, correct-looking way to check if two strings match, and it works perfectly for a functional test. The timing side channel is invisible in normal use, so an assistant produces the natural comparison without reaching for the constant-time function that security requires here.
The quick fix
- Use crypto.timingSafeEqual (Node) or hmac.compare_digest (Python) for secrets.
- Compare lengths first, since timingSafeEqual requires equal-length buffers.
- Apply this to API keys, tokens, signatures, and password-reset codes.