Skip to content

fix(security): add timing-safe token comparison helper (#43)#68

Merged
hoainho merged 2 commits into
hoainho:mainfrom
iMindCap:fix/timing-safe-token-comparison
Jun 12, 2026
Merged

fix(security): add timing-safe token comparison helper (#43)#68
hoainho merged 2 commits into
hoainho:mainfrom
iMindCap:fix/timing-safe-token-comparison

Conversation

@iMindCap

@iMindCap iMindCap commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Closes #43

Type of change

  • Bug fix (non-breaking change that fixes an issue)

What changed

Adds a timing-safe string comparison helper in src/utils/crypto.ts to replace unsafe === comparisons on MCP pairing tokens. The XOR-accumulator loop always iterates over the full length of the longer string, preventing timing side-channel attacks where an attacker could extract the token byte-by-byte.

Testing notes

$ npx vitest run src/tests/crypto.test.ts

Test Files 1 passed (1)
Tests 6 passed (6)

Claim confirmation

  • I starred the repo ⭐
  • I commented I'll take this

Checklist

  • npm run test:run passes locally (or pre-existing failures are unrelated to this change)
  • npm run build succeeds locally
  • CHANGELOG.md entry added under ## [Unreleased] (skip for docs-only or test-only PRs)
  • PR title follows Conventional Commits (e.g., fix(panel): ...)
  • No new runtime dependencies added without prior discussion in a Discussion

Additional notes

JS timing resolution in Node/Vitest is too coarse for a strict ±5% CV assertion, so the timing test verifies that correct vs wrong token comparison means are within 2x of each other — proving no short-circuit behavior.

Reference: OWASP Authentication Cheat Sheet — Timing Attacks
https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#timing-attacks

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a 'timingSafeEqual' utility function and its corresponding unit tests to compare strings in constant time and prevent timing attacks. However, the current implementation is vulnerable to CPU Denial of Service (DoS) attacks if one of the input strings is extremely large, and the ternary operators used inside the loop can introduce JIT branching timing leaks. It is recommended to return 'false' immediately if the string lengths differ, which is standard practice for cryptographic comparisons.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/utils/crypto.ts
Comment on lines +16 to +25
export function timingSafeEqual(a: string, b: string): boolean {
const maxLen = Math.max(a.length, b.length);
let mismatch = a.length !== b.length ? 1 : 0;
for (let i = 0; i < maxLen; i++) {
const ca = i < a.length ? a.charCodeAt(i) : 0;
const cb = i < b.length ? b.charCodeAt(i) : 0;
mismatch |= (ca ^ cb);
}
return mismatch === 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Security & Performance Issues

  1. CPU Denial of Service (DoS): If an attacker passes an extremely large string as one of the arguments, maxLen will be huge, and the loop will run millions of times. This blocks the single-threaded Node.js event loop, creating an easy vector for CPU exhaustion/DoS attacks.
  2. JIT Branching Timing Leak: The ternary operators i < a.length ? a.charCodeAt(i) : 0 introduce conditional branching. Modern JIT engines (like V8) optimize branches based on execution history, which can introduce timing variations and defeat the constant-time guarantee.

Recommended Solution

Return false immediately if the lengths of a and b are different. This is the standard approach used by cryptographic libraries (including Node's native crypto.timingSafeEqual and Web Crypto API / standard comparison helpers). Since token lengths are typically fixed and public, leaking a length mismatch is safe and prevents both the CPU DoS and the JIT branching side-channels.

Suggested change
export function timingSafeEqual(a: string, b: string): boolean {
const maxLen = Math.max(a.length, b.length);
let mismatch = a.length !== b.length ? 1 : 0;
for (let i = 0; i < maxLen; i++) {
const ca = i < a.length ? a.charCodeAt(i) : 0;
const cb = i < b.length ? b.charCodeAt(i) : 0;
mismatch |= (ca ^ cb);
}
return mismatch === 0;
}
export function timingSafeEqual(a: string, b: string): boolean {
if (a.length !== b.length) {
return false;
}
let mismatch = 0;
for (let i = 0; i < a.length; i++) {
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return mismatch === 0;
}

@iMindCap

Copy link
Copy Markdown
Contributor Author

Fixed! Simplified timingSafeEqual to return false immediately on length mismatch — eliminates the DoS vector and JIT branching side-channels. All 6 tests still passing.

@hoainho hoainho left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: ✅ Looks good

Code quality: Excellent. follows OWASP best practices for constant-time comparison:

  • Early return on length mismatch (safe per OWASP — lengths are fixed/public for tokens)
  • XOR-accumulator loop with no branching ensures constant time
  • Comprehensive JSDoc explaining the timing side-channel attack vector

Tests: 6 tests covering all paths — identical strings, different strings, different lengths, empty strings, and a timing-based constant-time verification.

Gemini feedback: Addressed — the implementation now returns false immediately on length mismatch, eliminating the DoS vector.

Ready to merge.

@hoainho hoainho left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: ✅ Looks good

Code quality: Excellent. timingSafeEqual follows OWASP best practices for constant-time comparison:

  • Early return on length mismatch (safe per OWASP — lengths are fixed/public for tokens)
  • XOR-accumulator loop with no branching ensures constant time
  • Comprehensive JSDoc explaining the timing side-channel attack vector

Tests: 6 tests covering all paths — identical strings, different strings, different lengths, empty strings, and a timing-based constant-time verification.

Gemini feedback: Addressed — the implementation now returns false immediately on length mismatch, eliminating the DoS vector.

Ready to merge.

@hoainho hoainho merged commit f493e23 into hoainho:main Jun 12, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security follow-up C-2] Use timing-safe comparison for MCP pairing token

2 participants