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>
29 lines
1.2 KiB
JavaScript
29 lines
1.2 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { timingSafeStrEqual } from '../../lib/auth/safe_compare.js';
|
|
|
|
// The owner bearer token is compared on every request. A naive === leaks
|
|
// length+prefix via timing; a naive crypto.timingSafeEqual THROWS when the two
|
|
// buffers differ in length. timingSafeStrEqual must be constant-time for equal
|
|
// lengths and return false (never throw) for mismatched lengths or bad input.
|
|
|
|
describe('timingSafeStrEqual', () => {
|
|
it('returns true for identical strings', () => {
|
|
expect(timingSafeStrEqual('vk_secrettoken', 'vk_secrettoken')).toBe(true);
|
|
});
|
|
|
|
it('returns false for same-length but different strings', () => {
|
|
expect(timingSafeStrEqual('aaaaaa', 'aaaaab')).toBe(false);
|
|
});
|
|
|
|
it('returns false (no throw) for different-length strings', () => {
|
|
expect(timingSafeStrEqual('short', 'a-much-longer-token')).toBe(false);
|
|
});
|
|
|
|
it('returns false for null / undefined / empty input', () => {
|
|
expect(timingSafeStrEqual(undefined, 'x')).toBe(false);
|
|
expect(timingSafeStrEqual('x', undefined)).toBe(false);
|
|
expect(timingSafeStrEqual('', '')).toBe(false);
|
|
expect(timingSafeStrEqual(null, null)).toBe(false);
|
|
});
|
|
});
|