Skip to content

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

Description

@hoainho

🌟 Before claiming this issue

Two quick steps before you open a PR:

  1. ⭐ Star the repository β€” low-friction signal that you'll follow through
  2. πŸ’¬ Comment I'll take this (or similar) below β€” prevents two contributors racing on the same issue

Full policy: CONTRIBUTING.md β†’ How to claim. If a claim is older than 7 days with no PR, you can reclaim it politely.


Tracks follow-up #2 from PR #17 security review. Blocked on mcp-server-v1 M3 (~Jul 28, 2026).

What

MCPPairingPanel compares the user-entered token to the expected token using === (constant-time-unsafe). Use a timing-safe comparison.

Why

=== short-circuits on the first byte difference, leaking the prefix-length-match through timing. For a 32-byte token, an attacker controlling the input can extract the correct token byte-by-byte via timing oracle in O(256 Γ— 32 Γ— N samples) requests. This is the textbook side-channel attack pattern.

The Web Crypto API has crypto.subtle.timingSafeEqual (proposed) β€” until that's universal, the manual fallback is a XOR-accumulator loop.

Acceptance criteria

  • MCPPairingPanel.tsx compares user input to expected token via a timing-safe helper (no === on the raw token strings)
  • Helper exported from src/utils/crypto.ts (or similar) with JSDoc + a comment citing the side-channel rationale
  • Unit test verifies the helper returns false for mismatched-but-same-length inputs AND for different-length inputs
  • Unit test verifies all execution paths take constant time within Β±5% (use performance.now() x 1000 iterations + std-dev assertion)

Implementation hint

// src/utils/crypto.ts
/**
 * Compare two strings in constant time relative to the longer string's length.
 * Use this for token / secret comparisons to avoid timing side-channel attacks.
 *
 * NOTE: Once Web Crypto's timingSafeEqual ships universally
 * (https://github.com/whatwg/webcrypto/issues/270), prefer that. For now:
 */
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;
}

Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedExtra attention is neededsecuritySecurity-related (see SECURITY.md for vulnerabilities)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions