From 6a5978d8cb54091b50dfe3ab4cdeb170f7da2687 Mon Sep 17 00:00:00 2001 From: obacollins-lab Date: Mon, 1 Jun 2026 10:50:52 +0100 Subject: [PATCH] fix(#689): Use timing-safe comparison for API key validation - Import timingSafeEqual from Node.js crypto module - Replace string equality (===) with timing-safe buffer comparison - Prevent timing attacks that could reveal key length and content - Handle buffer length mismatches gracefully - Maintain same authorization behavior with improved security --- api/src/routes/anchors.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/api/src/routes/anchors.ts b/api/src/routes/anchors.ts index 9f8c6a18..6331f4b1 100644 --- a/api/src/routes/anchors.ts +++ b/api/src/routes/anchors.ts @@ -1,4 +1,5 @@ import { Router, Request, Response } from 'express'; +import { timingSafeEqual } from 'crypto'; import { AnchorProvider, AnchorListResponse, AnchorDetailResponse } from '../types/anchor'; import { ErrorResponse } from '../types'; import { @@ -88,7 +89,22 @@ function requireAdminApiKey(adminApiKey?: string) { } const requestApiKey = req.header('x-api-key'); - if (!requestApiKey || requestApiKey !== adminApiKey) { + if (!requestApiKey) { + return sendError(res, 401, 'Unauthorized', 'UNAUTHORIZED'); + } + + // Use timing-safe comparison to prevent timing attacks + try { + const keysMatch = timingSafeEqual( + Buffer.from(requestApiKey), + Buffer.from(adminApiKey), + ); + if (!keysMatch) { + return sendError(res, 401, 'Unauthorized', 'UNAUTHORIZED'); + } + } catch { + // timingSafeEqual throws if buffers are different lengths + // This is expected for invalid keys, treat as unauthorized return sendError(res, 401, 'Unauthorized', 'UNAUTHORIZED'); }