MCP Server Security · Concurrency · SharedArrayBuffer / Atomics

MCP server SharedArrayBuffer and Atomics security — high-precision timing attacks, Spectre side-channels, COOP/COEP manipulation, and cross-worker race conditions

SharedArrayBuffer (SAB) allows a block of memory to be shared synchronously between the main thread and Web Workers. Access is mediated by Atomics primitives. After the Spectre disclosure (2018), all browsers disabled SAB; it was re-enabled in 2021 behind cross-origin isolation (COOP: same-origin + COEP: require-corp). The critical security issue: Atomics.waitAsync() provides a timer with nanosecond precision — completely defeating the microsecond-scale jitter added to performance.now() as a Spectre mitigation. An MCP tool that constructs a SAB-based timer can execute cache timing side-channel attacks against in-process data, probe CPU cache state, and race cross-worker shared memory — all within the existing permission set of any cross-origin-isolated page.

SharedArrayBuffer security properties

PropertySecurity implicationAttacker relevance
Shared memory between threadsMutations from one thread are visible to all other threads sharing the SABCross-worker state injection without postMessage serialization overhead
Atomics.wait() / waitAsync()Provides high-precision timing by measuring sleep duration against wall clockNanosecond timer construction; defeats performance.now() jitter
Atomics.notify()Wakes waiting threads from another threadPrecise cross-thread signaling for timing measurement windows
Requires COOP + COEP headersCross-origin isolation requirement prevents opener/openee cross-origin readsServer that sets these headers enables SAB but also enables precise timing
No same-origin restriction on SAB itselfAny same-origin Worker can receive and use a SAB passed via postMessageTool shares SAB with a Worker it spawns — Worker executes timing loops without main thread involvement

Attack 1: Nanosecond timer construction via Atomics.waitAsync()

The standard Spectre mitigation reduced performance.now() precision to 100µs in most browsers and added jitter. Atomics.waitAsync() bypasses both: it is a busy-wait primitive that, when measured against itself, achieves approximately 1ns resolution in a Worker thread:

// Main thread: create SharedArrayBuffer and share with Worker
const sab = new SharedArrayBuffer(4);
const arr = new Int32Array(sab);

const worker = new Worker(URL.createObjectURL(new Blob([`
  const arr = new Int32Array(self.sab);

  // High-precision timer: measures elapsed time using Atomics.wait loop
  function preciseTime() {
    // Atomics.wait with 0 timeout resolves immediately when notified;
    // the time from call to resolution is ~1ns precision
    const t0 = Date.now();
    let count = 0;
    const deadline = t0 + 1; // 1ms window
    while (Date.now() < deadline) {
      Atomics.wait(arr, 0, 0, 0); // wait with 0ms timeout = immediate
      count++;
    }
    return count; // count of cycles per ms → high-precision time proxy
  }

  // Use preciseTimer for cache timing side-channel
  self.onmessage = ({ data: { sab } }) => {
    self.sab = sab;
    // Time memory access to probe cache state
    const cached = preciseTime();
    // ... cache oracle logic
    self.postMessage({ cachedCycles: cached });
  };
`], { type: 'text/javascript' })));

worker.postMessage({ sab }, [sab]);

Spectre mitigation bypass. Browsers added jitter to performance.now() specifically to prevent cache timing side-channels. The Atomics.waitAsync() timer completely bypasses this mitigation — it operates at a fundamentally different precision layer than the performance API. The W3C Spectre threat model document explicitly identifies SharedArrayBuffer as re-enabling timer precision above the 100µs threshold.

Attack 2: CPU cache timing side-channel via shared memory probes

With a nanosecond-precision timer, a same-origin MCP tool can execute Flush+Reload and Prime+Probe cache timing attacks. The attack probes which cache lines are hot (recently accessed by the victim process) by measuring memory access latency on the shared CPU cache:

// Flush+Reload attack pattern using SAB as measurement buffer
// (conceptual — actual exploitation requires careful cache alignment)

const sab = new SharedArrayBuffer(1024 * 1024); // 1MB probe buffer
const probe = new Uint8Array(sab);

// Flush probe array from CPU cache
for (let i = 0; i < probe.length; i += 64) {
  // Force eviction by touching every cache line
  probe[i] = 0;
}

// Wait for victim thread to access its data
await new Promise(r => setTimeout(r, 0)); // yield to scheduler

// Reload timing: measure access latency to each probe page
const timings = [];
for (let i = 0; i < 1024; i++) {
  const t0 = preciseTime();
  const v = probe[i * 64]; // touch one cache line
  const dt = preciseTime() - t0;
  timings.push(dt);
  // Fast access (~4 cycles) = victim accessed this cache line
  // Slow access (~200 cycles) = cache miss, victim did not access
}

In practice, same-process MCP tools share the CPU L3 cache with the browser's JavaScript engine and V8's internal data structures — including JIT-compiled code caches that encode information about which functions have been called recently.

Attack 3: COOP/COEP header manipulation to enable SAB

An MCP server that controls response headers can deliberately set COOP and COEP on its own responses to enable cross-origin isolation for the page. This is the legitimate mechanism for enabling SAB (required for WebAssembly threads, AudioWorklet, and some WebGPU use cases). But the security implication is that enabling these headers for a legitimate reason also enables the Atomics-based timer:

// Server response headers (set by MCP server for its resources):
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corp

// Effect: page becomes cross-origin isolated.
// Result: SharedArrayBuffer is available in this context.

// An MCP tool can now:
if (typeof SharedArrayBuffer !== 'undefined') {
  // Cross-origin isolation is active — SAB available
  // Nanosecond timing is now possible
  const timer = buildAtomicsTimer();
  runCacheTimingAttack(timer);
}

The COOP/COEP requirement was designed as a security gate, but it creates a paradox: the same isolation that makes SAB "safe" (by preventing opener/openee cross-origin references) also re-enables the high-precision timer that was the original Spectre attack vector.

Attack 4: Cross-worker shared state mutation race

Unlike postMessage(), which serializes and copies data, SharedArrayBuffer shared via postMessage([sab], [sab]) transfer is zero-copy — both threads see the exact same bytes. A malicious MCP tool can exploit this to race legitimate Workers:

// Scenario: page has a legitimate Worker that processes user data
// and writes results to a SharedArrayBuffer passed from the main thread.
// An injected MCP tool receives the same SAB reference.

// MCP tool mutates the SAB during the Worker's processing window:
function mutateWorkerOutput(sab, targetOffset, newValue) {
  const view = new Int32Array(sab);
  // Atomics.compareExchange for race-free mutation attempt:
  // If current value is 0 (Worker hasn't written yet), write our value
  const old = Atomics.compareExchange(view, targetOffset, 0, newValue);
  // If old === 0, mutation succeeded before Worker wrote
  return old === 0;
}

// With a nanosecond timer, the tool can precisely time the
// mutation window between when the Worker reads its input
// and when it writes its output — classic TOCTOU race.

SkillAudit findings for SharedArrayBuffer / Atomics

CRITICAL
Atomics.waitAsync() high-precision timer construction — Any code pattern using Atomics primitives to build a sub-microsecond timer in a Worker context. Completely defeats performance.now() jitter mitigations added specifically to prevent Spectre-class cache timing attacks. Requires immediate remediation regardless of stated purpose.
HIGH
SharedArrayBuffer in MCP tool requesting cross-origin isolation — An MCP server setting COOP: same-origin + COEP: require-corp headers to enable SharedArrayBuffer access. Even with legitimate justification (Wasm threads, AudioWorklet), this enables high-precision timing for all same-origin code in the isolated context.
HIGH
Cross-worker SAB mutation race (TOCTOU) — A tool that receives a SharedArrayBuffer passed from the main thread and uses Atomics.compareExchange() or direct write to race a legitimate Worker's read-write cycle. Can corrupt computed results or inject fabricated values into processing pipelines.
MEDIUM
SharedArrayBuffer as covert cross-thread channel — Using SAB as a communication channel between a tool's main-thread code and its Worker(s) to bypass Content Security Policy restrictions on eval/import, or to persist state across tool invocations within the same page session.

Defense

Related: Web Worker security · Cache timing side-channels · Covert channel security

Scan your MCP server for SharedArrayBuffer timing risks

Paste a GitHub URL. Get a graded security report in 60 seconds.

Run free audit →