From dd8eefe3f9b75467b372803daf44afea7e1f9026 Mon Sep 17 00:00:00 2001 From: KoalaDev <6156589+Shik3i@users.noreply.github.com> Date: Tue, 16 Jun 2026 04:23:20 +0200 Subject: [PATCH] =?UTF-8?q?fix(security):=20actually=20prevent=20timing=20?= =?UTF-8?q?leak=20=E2=80=94=20eagerly=20evaluate=20timingSafeEqual?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix had a subtle bug: JavaScript && short-circuits, so crypto.timingSafeEqual() was skipped when buffer lengths differed (sameLength was false). The dummy buffer was allocated but never compared, leaving the original length-based timing leak intact. Now timingSafeEqual is eagerly assigned to a const before the && guard, guaranteeing it runs in constant time on every auth attempt regardless of whether the length guess was correct. --- server/ops.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/ops.js b/server/ops.js index f956e92..de79c6e 100644 --- a/server/ops.js +++ b/server/ops.js @@ -37,9 +37,13 @@ export function isAdminMetricsAuthorized(authHeader, adminToken) { // timingSafeEqual throws on different-length buffers, so when lengths // differ we compare against a zeroed buffer of the provided length // (guaranteed mismatch, constant time). + // NOTE: timingSafeEqual must be evaluated eagerly (assigned to const) + // before the && short-circuit, otherwise it's skipped on length mismatch + // and the timing leak remains. const sameLength = expectedBuffer.length === providedBuffer.length; const compareBuf = sameLength ? expectedBuffer : Buffer.alloc(providedBuffer.length); - return sameLength && crypto.timingSafeEqual(compareBuf, providedBuffer); + const equal = crypto.timingSafeEqual(compareBuf, providedBuffer); + return sameLength && equal; } export function isAdminMetricsTokenStrong(adminToken, minLength = 32) {