CVE Patch Review

GHSA-HGV7-V322-MMGR: Root-cause Fix for SvelteKit query.batch SSR Cross-Talk

GHSA-HGV7-V322-MMGR · Updated 2026-05-21 Root-cause

Summary

The patch addresses a request-isolation flaw in SvelteKit SSR where query.batch state was previously shared in module scope, allowing concurrent requests to coalesce across users. The fix moves batching state into per-request remote state and adds explicit typing for batch storage, aligning the implementation with request-scoped isolation described in the advisory.

Analysis

Vulnerability

GHSA-HGV7-V322-MMGR describes an SSR state-isolation failure in SvelteKit query.batch where concurrent remote requests could intermingle and expose session-bound data across users. The vulnerable implementation kept batching state in a module-level variable:

/** @type {Map<string, { get_validated: () => MaybePromise<any>, resolvers: Array<{resolve: (value: any) => void, reject: (error: any) => void}> }>} */
	let batching = new Map();

Because this map was not tied to a specific request context, SSR concurrency could cause payloads from separate requests to be deduplicated or resolved together within the same macrotask. In practice, that creates cross-request contamination: one user's request can observe data validated or resolved under another user's session. The issue is consistent with shared mutable state in server runtime code, not merely an input-validation bug.

The advisory and patch metadata indicate the fix is in SvelteKit 2.60.1. The core security problem is request context leakage caused by globally scoped batching state rather than per-request storage.

Patch

The patch in the official commit replaces the global batching map with request-scoped storage under state.remote.batches. Instead of a single process-wide map, the code now looks up a batch map associated with the current request state and further keys it by __.id:

const batches = (state.remote.batches ??=
				/** @type {NonNullable<typeof state.remote.batches>} */ (new Map()));
			let batched = batches.get(__.id);
			if (!batched) {
				batched = new Map();
				batches.set(__.id, batched);
			}
			const entry = batched.get(payload);
			batched.set(payload, {
			if (batched.size > 1) return;
				batches.delete(__.id);

The server request state in respond.js is extended to include batches: null, making batch tracking an explicit part of request-local remote state. The internal type definitions are updated accordingly, adding a strongly typed batches map and tightening related types for forms and reconnects. These changes are important because they document and enforce the intended lifetime and ownership of the batching structure.

Per the issue summary, the release also uses AsyncLocalStorage to isolate request contexts. The diff shown here demonstrates the concrete runtime effect of that design: batch aggregation is no longer module-global and is instead anchored to request state.

Review

Pros

  • The patch directly addresses the root cause: shared mutable batching state was moved from module scope into request-scoped state.
  • Isolation is improved at the correct abstraction boundary. query.batch behavior remains intact within a request while preventing cross-request coalescing.
  • The nested map keyed by batch ID and payload preserves batching semantics without relying on process-global coordination.
  • respond.js now explicitly initializes remote.batches, which makes request ownership visible in the runtime model.
  • The type updates in internal.d.ts reduce ambiguity and help prevent regressions by documenting the expected structure of batch state.
  • The patch is narrowly scoped and low-risk from a compatibility perspective because it changes state placement rather than public API behavior.

Cons

  • The diff excerpt does not itself show the full AsyncLocalStorage wiring, so reviewers must rely on the broader release context and surrounding runtime state flow to confirm there are no remaining paths that access shared state incorrectly.
  • The fix is specific to query.batch; it does not by itself prove that all other SSR remote-function caches or deduplication mechanisms are free from similar request-scope mistakes.
  • There is no visible regression test in the provided snippets demonstrating concurrent SSR requests with distinct sessions, which would be valuable for preventing recurrence.

Verdict

Root-cause.

This patch remedies the underlying isolation flaw rather than masking symptoms. The vulnerable design used a module-level Map for batching, which is inherently unsafe in concurrent SSR because it spans requests. The patched design relocates that state into state.remote.batches, making batching request-local and keyed by batch ID. That is the correct security boundary for preventing session cross-talk and data exposure described in the advisory. Based on the provided diff and references, this is a substantive fix, not a bandaid.

References: official patch commit, GitHub Security Advisory, report summary.

Sources