GHSA-HGV7-V322-MMGR: Root-cause Fix for SvelteKit query.batch SSR Cross-Talk
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.batchbehavior 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.jsnow explicitly initializesremote.batches, which makes request ownership visible in the runtime model.- The type updates in
internal.d.tsreduce 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.
Recommended Labs
Try this vulnerability pattern yourself with hands-on labs.
- Steam.js
Best match for the SvelteKit issue because it is a JavaScript hands-on lab tagged to CWE-366/CWE-362/CWE-367 and OWASP A01:2021/A05:2021, which align with request concurrency, race conditions, and isolation failures that can cause cross-user state mix-ups.
- Energy.js
Good companion lab for the data exposure side of this advisory. It focuses on information leakage themes including CWE-200/CWE-204/CWE-209-style disclosure patterns, helping reinforce defensive handling of sensitive session data and safe error/output behavior in JavaScript services.
- Steam.ts
Useful if you want a TypeScript-flavored lab closer to modern SSR/full-stack app development. It covers the same race-condition family as Steam.js and is relevant for practicing defensive fixes around async request context separation and shared mutable state.