Owner bearer token was compared with === / !==, which short-circuits on the first differing byte and leaks token length+prefix via response timing (security-sweep-2026-06-01.md). New timingSafeStrEqual (crypto.timingSafeEqual with a length pre-check so it never throws on length mismatch); wired into both owner.js and agent_auth.js. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
24 lines
871 B
JavaScript
24 lines
871 B
JavaScript
import crypto from 'node:crypto';
|
|
|
|
/**
|
|
* Constant-time string comparison for secrets (e.g. the owner bearer token).
|
|
*
|
|
* Plain `===` short-circuits on the first differing byte, leaking length and
|
|
* prefix via timing. `crypto.timingSafeEqual` is constant-time but THROWS when
|
|
* the two buffers differ in length — so we length-check first (length is not
|
|
* the secret) and only then do the constant-time compare. Returns false for
|
|
* any falsy / non-string input rather than throwing.
|
|
*
|
|
* @param {string} a
|
|
* @param {string} b
|
|
* @returns {boolean}
|
|
*/
|
|
export function timingSafeStrEqual(a, b) {
|
|
if (typeof a !== 'string' || typeof b !== 'string') return false;
|
|
if (a.length === 0 || b.length === 0) return false;
|
|
const ab = Buffer.from(a);
|
|
const bb = Buffer.from(b);
|
|
if (ab.length !== bb.length) return false;
|
|
return crypto.timingSafeEqual(ab, bb);
|
|
}
|