From cc97e0d371c9427908081537d730a7dd7dc4b038 Mon Sep 17 00:00:00 2001 From: KoalaDev <6156589+Shik3i@users.noreply.github.com> Date: Tue, 16 Jun 2026 04:21:15 +0200 Subject: [PATCH] fix(security): prevent admin token length leak via timing side-channel isAdminMetricsAuthorized() returned early when buffer lengths differed, allowing an attacker to discover the token length by measuring response time. Now always calls crypto.timingSafeEqual (using a zeroed dummy buffer on length mismatch) so every auth attempt takes constant time regardless of whether the length guess was correct. Reported-by: Kaia-Alenia --- server/ops.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/ops.js b/server/ops.js index d286d3b..f956e92 100644 --- a/server/ops.js +++ b/server/ops.js @@ -33,8 +33,13 @@ export function isAdminMetricsAuthorized(authHeader, adminToken) { const expectedBuffer = Buffer.from(adminToken); const providedBuffer = Buffer.from(provided); - if (expectedBuffer.length !== providedBuffer.length) return false; - return crypto.timingSafeEqual(expectedBuffer, providedBuffer); + // Always run timingSafeEqual to prevent length-based timing leaks. + // timingSafeEqual throws on different-length buffers, so when lengths + // differ we compare against a zeroed buffer of the provided length + // (guaranteed mismatch, constant time). + const sameLength = expectedBuffer.length === providedBuffer.length; + const compareBuf = sameLength ? expectedBuffer : Buffer.alloc(providedBuffer.length); + return sameLength && crypto.timingSafeEqual(compareBuf, providedBuffer); } export function isAdminMetricsTokenStrong(adminToken, minLength = 32) {