From e937da62ebf3d842d26fc285e434de567fe9b67c Mon Sep 17 00:00:00 2001 From: Sertug17 <104278804+Sertug17@users.noreply.github.com> Date: Fri, 7 Aug 2026 23:48:38 +0300 Subject: [PATCH] fix(gate): use timingSafeEqual to prevent timing attacks on password check The previous comparison used !== which short-circuits on the first mismatched character. An attacker measuring response latency across many requests can converge on the correct password one character at a time. Replace with node:crypto timingSafeEqual, which always takes constant time regardless of where the buffers differ. Length is checked first (not timing-sensitive) since timingSafeEqual requires equal-length buffers. --- app/api/gate/route.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/api/gate/route.ts b/app/api/gate/route.ts index 6cd1b78..7ab38f5 100644 --- a/app/api/gate/route.ts +++ b/app/api/gate/route.ts @@ -1,3 +1,5 @@ +import { timingSafeEqual } from 'node:crypto'; + import { NextResponse } from 'next/server'; // Verifies the temporary site password (see middleware.ts) and, on success, @@ -27,7 +29,22 @@ export async function POST(request: Request) { provided = ''; } - if (provided !== password) { + // Use timingSafeEqual to prevent timing attacks: a naive string comparison + // (`provided !== password`) returns early on the first mismatched character, + // leaking information about how many leading characters are correct. An + // attacker can measure response latency across many requests and converge on + // the password one character at a time. timingSafeEqual always takes the same + // amount of time regardless of where the buffers differ. + // + // Buffers must be the same length for timingSafeEqual, so a length mismatch + // is checked first (and is itself not timing-sensitive). + const providedBuf = Buffer.from(provided); + const passwordBuf = Buffer.from(password); + const match = + providedBuf.length === passwordBuf.length && + timingSafeEqual(providedBuf, passwordBuf); + + if (!match) { return NextResponse.json({ ok: false }, { status: 401 }); }