From 3e5b23ac7c58fd8ea8247609ee3c53a1f81c1176 Mon Sep 17 00:00:00 2001 From: ifeoluwaaj Date: Sun, 7 Jun 2026 21:12:17 +0000 Subject: [PATCH] fix(security): prevent timing side-channel in constantTimeEquals length comparison Both mcp-auth.ts and hosted-ui-auth.ts had a constantTimeEquals function that returned false immediately when buffer lengths differed, leaking the expected API key length via timing side-channel. Fix: pad both buffers to the maximum length before comparing, ensuring constant-time behavior regardless of input lengths. --- src/lib/server/hosted-ui-auth.ts | 8 +++++++- src/lib/server/mcp-auth.ts | 13 +++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/lib/server/hosted-ui-auth.ts b/src/lib/server/hosted-ui-auth.ts index 9954500b..f786ea7d 100644 --- a/src/lib/server/hosted-ui-auth.ts +++ b/src/lib/server/hosted-ui-auth.ts @@ -58,7 +58,13 @@ const hostedUiSessions = new Map< function constantTimeEquals(left: string, right: string): boolean { const leftBuffer = Buffer.from(left); const rightBuffer = Buffer.from(right); - return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer); + // Use the longer length to prevent timing leak on length comparison + const maxLen = Math.max(leftBuffer.length, rightBuffer.length); + const leftPadded = Buffer.alloc(maxLen, 0); + const rightPadded = Buffer.alloc(maxLen, 0); + leftBuffer.copy(leftPadded); + rightBuffer.copy(rightPadded); + return timingSafeEqual(leftPadded, rightPadded) && leftBuffer.length === rightBuffer.length; } function hashSessionId(sessionId: string): string { diff --git a/src/lib/server/mcp-auth.ts b/src/lib/server/mcp-auth.ts index 27fdb313..593cecab 100644 --- a/src/lib/server/mcp-auth.ts +++ b/src/lib/server/mcp-auth.ts @@ -9,12 +9,13 @@ const rateLimitBuckets = new Map(); function constantTimeEquals(left: string, right: string): boolean { const leftBuffer = Buffer.from(left); const rightBuffer = Buffer.from(right); - - if (leftBuffer.length !== rightBuffer.length) { - return false; - } - - return timingSafeEqual(leftBuffer, rightBuffer); + // Use the longer length to prevent timing leak on length comparison + const maxLen = Math.max(leftBuffer.length, rightBuffer.length); + const leftPadded = Buffer.alloc(maxLen, 0); + const rightPadded = Buffer.alloc(maxLen, 0); + leftBuffer.copy(leftPadded); + rightBuffer.copy(rightPadded); + return timingSafeEqual(leftPadded, rightPadded) && leftBuffer.length === rightBuffer.length; } interface ApiKeyExtractionOptions {