Axios: ReDoS (O(N²)) in shouldBypassProxy host normalization, reachable via untrusted redirect LocationGHSA-mghh-pgcx-3jjj
Fix: axios/axios#11141GHSA-mghh-pgcx-3jjj is a Uncontrolled Resource Consumption vulnerability in axios. A fix is available for axios — see the affected versions and patch details below.
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
Exploitation and automatability from CISA’s SSVC triage for GHSA-mghh-pgcx-3jjj.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
Real-World Exposure
How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.
axiosnpmDescription
Summary
Axios shouldBypassProxy() normalizes hostnames with hostname.replace(/\.+$/, ''). For a hostname shaped as many dots followed by a non-dot, the anchored regex can perform quadratic backtracking. Because axios re-evaluates proxy bypass rules for redirected requests, a malicious server can trigger this synchronous work through a crafted redirect Location.
The issue affects Node.js applications that use environment proxy variables with NO_PROXY and allow redirects.
Impact
An attacker-controlled server can return a redirect whose hostname causes the axios process to spend significant CPU time in synchronous hostname normalization. During this time, the Node.js event loop is blocked and the application cannot handle other work on that thread.
This is an availability-only issue. It does not disclose request data or modify requests.
Affected Functionality
Affected:
- Node.js HTTP adapter.
- Environment proxy handling through
HTTP_PROXYorHTTPS_PROXY. NO_PROXYorno_proxyset to a non-empty value.- Redirects followed by axios or
follow-redirects.
Not affected:
- Browser adapters.
- Requests with
proxy: false. - Requests with no
NO_PROXYvalue. - Requests with
maxRedirects: 0, unless application code manually follows the malicious redirect and re-enters axios.
Technical Details
lib/helpers/shouldBypassProxy.js contains:
return unmapIPv4MappedIPv6(hostname.replace(/\.+$/, ''));
When the hostname is "." * n + "a", the $ anchor causes the regex engine to retry the dot run from many positions before it fails. setProxy() invokes shouldBypassProxy(location) after getProxyForUrl(location) returns a proxy, including on redirect hops via beforeRedirects.proxy.
Local timing on axios 1.18.1 showed increasing cost for crafted hostnames: about 1 ms at 1000 dots, 6.9 ms at 3000 dots, and 34.5 ms at 6000 dots. The growth is consistent with the submitted quadratic claim while avoiding long-running payloads.
Proof of Concept of Attack
Constrained helper-level demonstration:
import shouldBypassProxy from 'axios/lib/helpers/shouldBypassProxy.js';
process.env.NO_PROXY = 'example.com';
shouldBypassProxy('http://' + '.'.repeat(6000) + 'a/');
In the full adapter path, a malicious server can return that hostname in a 302 Location header while the client has proxy environment variables and NO_PROXY configured.
Workarounds
Disable automatic redirects for requests to untrusted servers, or avoid environment proxy handling for those requests with proxy: false when appropriate. Operators can also avoid broad untrusted redirect-following in services where event-loop availability is critical.
Summary
shouldBypassProxy normalizes a host with hostname.replace(/.+$/, ''). On a host of the shape <many dots><non-dot> (e.g. "." × 40000 + "a"), this anchored regex backtracks quadratically (O(N²)), synchronously starving the Node.js event loop. Because axios re-evaluates the proxy on every redirect using the new Location host, a malicious server can return a crafted 302 Location and freeze the client's event loop for seconds per redirect — a denial of service.
Details
- Affected code: lib/helpers/shouldBypassProxy.js → normalizeNoProxyHost: hostname.replace(/.+$/, '') (one pass in 1.18.1). The open PR #11029 adds a second /.+$/ pass in normalizeIPAddress, doubling the cost (not the origin).
- Root cause: /.+$/ is O(N²) on a long run of dots that is not at the end of the string — the $ anchor forces the engine to backtrack the entire dot-run from every start position.
- Trust boundary (per THREATMODEL): the redirect Location is untrusted (T-2: "axios to network … redirect Location … untrusted"). The caller requests a benign URL; the malicious host arrives via the server's 302. This is not the T-1 caller-supplied-URL non-goal.
- Call chain: setProxy(options, configProxy, location, isRedirect, …) (lib/adapters/http.js) → env-proxy branch → getProxyForUrl(location) returns a proxy → if (!shouldBypassProxy(location)) → normalizeNoProxyHost(parsed.hostname.toLowerCase()) runs /.+$/. setProxy is re-invoked on the redirect hop with the untrusted Location; new URL() retains the long dot-run in .hostname.
- Preconditions: proxy configured via environment (HTTP_PROXY/HTTPS_PROXY, trusted per THREATMODEL T-3, common in CI/containers/enterprise) + NO_PROXY set + redirects followed (default maxRedirects: 5).
PoC
Self-contained, no network — imports axios's own helper:
// node poc.mjs (run next to an axios install)
import sbp from './node_modules/axios/lib/helpers/shouldBypassProxy.js';
process.env.NO_PROXY = 'example.com';
for (const n of [5000, 10000, 20000, 40000]) {
const url = 'http://' + '.'.repeat(n) + 'a/'; // arrives as an untrusted 302 Location host
const t0 = process.hrtime.bigint();
sbp(url); // returns false (correct no-bypass) — but O(N^2) slow
console.log(N=${n}: ${(Number(process.hrtime.bigint()-t0)/1e6)|0} ms);
}
// Measured on 1.18.1: N=5000 ~43ms, 10000 ~160ms, 20000 ~618ms, 40000 ~2499ms; benign host ~0.05ms.
Driven through the real http-adapter __setProxy on the redirect path (setProxy(…, isRedirect=true)), a 10 ms timer fires 0 times during the ~2.4 s block at N=40000 — full event-loop starvation.
Impact
Denial of service (event-loop starvation) on any axios client that uses an environment proxy with NO_PROXY set and follows redirects, when a server it contacts returns a crafted redirect Location. No data exposure or RCE. Same impact class as the accepted DoS advisories GHSA-62hf-57xw-28j9 (toFormData recursion) and the maxContentLength response-size DoS.
Suggested fix
Replace the regex trailing-dot strip with a linear trim: let end = hostname.length; while (end > 0 && hostname.charCodeAt(end - 1) === 46 /* '.' */) end--; hostname = hostname.slice(0, end); Also drop the redundant second /.+$/ pass in PR #11029's normalizeIPAddress.
</details>Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | axios | ≥ 1.15.0&&< 1.20.0 | 1.20.0npm install axios@1.20.0 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for axios, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update axios to 1.20.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-mghh-pgcx-3jjj is resolved across your whole dependency graph.
Workarounds
Cap what an attacker can consume: apply request size, rate and timeout limits in front of the affected component, and run it with memory and CPU limits so exhaustion degrades one worker rather than the whole service.
Fixing This On Your OS
If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.
| Product | Fixed in | Advisory |
|---|---|---|
| Red Hat Hardened Images | grafana13-1-main-13.1.6-0.6.hum1 | RHSA-2026:74869 |
| Red Hat Hardened Images | grafana12-4-main-12.4.12-0.3.hum1 | RHSA-2026:74871 |
| Red Hat Hardened Images | grafana13-2-main-13.2.1-0.9.hum1 | RHSA-2026:74872 |
Frequently Asked Questions
Is GHSA-mghh-pgcx-3jjj in your dependencies?
Find it across npm, including transitive dependencies.