← All fixes

Fix it

Comparing a secret with === (timing attack) — how to fix it

Medium severityCWE-208 (Observable Timing Discrepancy)

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.

Want to know if this pattern is already in a repo you shipped? Scan a public repo free, no account needed.

Scan a repo →
Timing-Unsafe Secret Comparison (===) — How to Fix It: Prbl