Vulnerability DatabaseGHSA-39r7-wfcx-99xg

GHSA-39r7-wfcx-99xg: 
JavaScript vulnerability analysis and mitigation

Summary

The route cache in nitropack 2.x (defineCachedEventHandler, also used for the cache, swr and isr route rules) did not take the HTTP method into account. It also stored and replayed per-user response headers. As a result:

  1. A POST (or any other method) to a cached URL could store its response under the same key a later GET reads. Every user then received that response until it expired. In the other direction, a POST to a cached URL was answered from the cached GET entry and its handler never ran.
  2. Set-Cookie headers set by a cached handler were stored in the cache and sent to every user served from that entry. This leaks session identifiers and allows session fixation.
  3. A handler's Cache-Control: private, no-store or no-cache was ignored. The response was still cached and served to other users, and the header was rewritten to a shared-cacheable value (s-maxage=… / max-age=…), so CDNs in front of the app could cache it too.
  4. A response Vary header was ignored when storing and serving entries.

Details

defineCachedEventHandler in src/runtime/internal/cache.ts built its cache key only from the request path (including the query string) and the optional varies request headers. The method was never consulted, and the wrapper had no method guard, so all methods for a URL shared one entry. Handlers wrapped by a cache/swr/isr route rule all share the nitro/routes group, so the per-URL key is the only separation. After the handler ran, all response headers (including set-cookie) were stored in the entry. The wrapper then replayed them on every cache hit, appending set-cookie to each response. The wrapper also always overwrote cache-control with values derived from maxAge/swr, regardless of what the handler set. The response Vary header was not consulted either. A response that varies on a request header not listed in varies (for example Accept-Language or Authorization) was stored under one key and served to all clients. Request coalescing made the cookie issue worse: concurrent requests for the same key share one handler invocation, so they also shared its per-user response.

Proof of concept

// nitro.config.ts
export default defineNitroConfig({
  routeRules: {
    "/cached": { cache: { maxAge: 60 } },
  },
});
// routes/cached.ts
export default defineEventHandler((event) => {
  // e.g. a session cookie issued during render
  setCookie(event, "sid", crypto.randomUUID());
  return { method: event.method, renderedAt: Date.now() };
});
npx nitropack build && node .output/server/index.mjs &

# 1. attacker
curl -i -X POST http://localhost:3000/cached

# 2. any other user, for the next 60s
curl -i http://localhost:3000/cached

Step 2 returns the cached POST response ("method": "POST") together with the attacker's Set-Cookie: sid=….

Impact

  • Cache poisoning / wrong responses: an unauthenticated attacker can fill a cached route's entry with the response to a non-GET request, which all users then receive for the maxAge window (plus any stale-while-revalidate period). The attacker's control over the content is limited: request headers are stripped and the body is not passed. Side-effecting POST/PUT/DELETE requests to cached URLs silently return the cached body and never run.
  • Session leakage / fixation: any cached route whose handler sets a cookie (for example a session, CSRF or locale cookie set during Nuxt SSR on an swr/isr page) sends that cookie to every subsequent visitor.
  • Private data in shared caches: responses explicitly marked private/no-store/no-cache, or varying on request headers via Vary, were cached by Nitro and served to other users. Responses were also advertised as cacheable to downstream CDNs. This affects nitropack v2, including Nuxt 3 and 4 apps that use swr/isr/cache route rules. Applications that do not use defineCachedEventHandler/cachedEventHandler or the cache/swr/isr route rules are not affected.

Patches

Fixed in nitropack 2.13.6:

  • Only GET and HEAD requests read or write the cache. Other methods (and Range requests) always run the handler.
  • Responses that set cookies are never stored, and Set-Cookie is only sent to the request that produced it.
  • Responses with Cache-Control: private, no-store or no-cache are never stored, and the handler's own Cache-Control is kept.
  • Responses whose Vary is * or names a request header not listed in varies are never stored.

Workarounds

If you cannot upgrade:

  • don't set cookies or return per-user data from cached handlers or from routes with cache/swr/isr rules;
  • reject non-GET/HEAD requests to cached routes at your CDN, reverse proxy or in a middleware that runs before the cached handler.

Source: NVD

Related JavaScript vulnerabilities:

CVE ID

Severity

Score

Technologies

Component name

CISA KEV exploit

Has fix

Published date

GHSA-rhhp-7488-3qwfMEDIUM6.5
  • JavaScript logoJavaScript
  • nitropack
NoYesNov 02, 2026
GHSA-pqxc-r4h5-jc95MEDIUM6.1
  • JavaScript logoJavaScript
  • nitropack
NoYesNov 02, 2026
GHSA-39r7-wfcx-99xgMEDIUM4.8
  • JavaScript logoJavaScript
  • nitropack
NoYesNov 02, 2026
GHSA-xrjv-fc7j-8f77HIGHN/A
  • JavaScript logoJavaScript
  • @graphql-tools/batch-execute
NoNoNov 02, 2026
GHSA-7p5p-67m9-5c8jHIGHN/A
  • JavaScript logoJavaScript
  • @graphql-tools/executor-http
NoNoNov 02, 2026

Free Vulnerability Assessment

Benchmark your Cloud Security Posture

Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.

Request assessment

Additional Wiz resources

Get a personalized demo

Ready to see Wiz in action?

"Best User Experience I have ever seen, provides full visibility to cloud workloads."
David EstlickCISO
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
Adam FletcherChief Security Officer
"We know that if Wiz identifies something as critical, it actually is."
Greg PoniatowskiHead of Threat and Vulnerability Management