Summary
app/api/gate/route.ts compares the submitted password against SITE_PASSWORD using a plain !== string comparison. JavaScript string comparison short-circuits on the first mismatched character, meaning the operation takes slightly longer when more leading characters are correct.
Impact
An attacker with network access to /api/gate can send many requests with varying passwords and measure response latency. By observing which prefixes produce longer responses, they can converge on the correct password one character at a time reducing a brute-force search from exponential to linear.
Root cause
// app/api/gate/route.ts
if (provided !== password) { // ← timing-sensitive
Fix
Replace with timingSafeEqual from node:crypto, which always takes constant time regardless of where the buffers differ:
const providedBuf = Buffer.from(provided);
const passwordBuf = Buffer.from(password);
const match =
providedBuf.length === passwordBuf.length &&
timingSafeEqual(providedBuf, passwordBuf);
References
Summary
app/api/gate/route.tscompares the submitted password againstSITE_PASSWORDusing a plain!==string comparison. JavaScript string comparison short-circuits on the first mismatched character, meaning the operation takes slightly longer when more leading characters are correct.Impact
An attacker with network access to
/api/gatecan send many requests with varying passwords and measure response latency. By observing which prefixes produce longer responses, they can converge on the correct password one character at a time reducing a brute-force search from exponential to linear.Root cause
Fix
Replace with
timingSafeEqualfromnode:crypto, which always takes constant time regardless of where the buffers differ:References