← All Case Studies
CVE-2024-39338 HIGH — CVSS 8.7

axios SSRF via NO_PROXY Environment Variable Bypass

axios < 1.7.4 does not correctly honor the NO_PROXY environment variable, allowing internal network access via hostnames that should be excluded from proxying.

Affected axios < 1.7.4 (all versions before August 2024)
Patched axios ≥ 1.7.4 (August 2024)
Package axios npm (~36M weekly downloads)
CWE CWE-918 (SSRF)
// what happened
  • Affected: axios < 1.7.4 (npm package, ~36M weekly downloads)
  • The vulnerable pattern: API proxy routes that accept a user-controlled URL parameter and pass it to axios.get(url) — the attacker bypasses NO_PROXY by controlling the hostname format
  • Root cause: axios's proxy-checks module did not correctly parse the NO_PROXY environment variable. Hostnames with unusual formats (e.g. containing dots in unexpected positions, or using IPv4/IPv6 representations that should be excluded) were not matched against the NO_PROXY list, causing axios to route the request through the proxy when it should have been direct.
  • Fix: commit in axios 1.7.4 adds a correct implementation of NO_PROXY parsing following the WHATWG URL standard and the common proxy bypass convention
  • Impact: SSRF to cloud metadata endpoints (169.254.169.254), internal services, database ports, and private network ranges. An attacker uses a proxy route in the application to reach internal resources that NO_PROXY should have protected. Especially severe when NO_PROXY=169.254.169.254,localhost is set but axios doesn't recognize the metadata IP as NO_PROXY-exempt.

Node.js and many HTTP clients use the NO_PROXY environment variable to specify hostnames that should bypass the system proxy. This is a critical security mechanism — for example, setting NO_PROXY=169.254.169.254 prevents tools from leaking cloud metadata credentials through the proxy when making requests to the metadata endpoint.

axios < 1.7.4's proxy-checks implementation incorrectly parsed the NO_PROXY variable. The parsing logic failed to handle certain hostname formats — particularly IPv4 addresses expressed in non-standard ways, and hostnames that contained characters that should trigger NO_PROXY matching but didn't due to a regex or string-comparison bug.

An attacker identifies a proxy route handler (e.g. GET /proxy?url=https://target) in the application. The handler passes the url query parameter to axios.get(). By crafting the target URL with a hostname that bypasses axios's NO_PROXY check, the attacker routes the request through the system proxy — which logs or forwards the request — rather than making a direct connection. The attacker exfiltrates data from internal services.

Real-world impact: When a service sets NO_PROXY=169.254.169.254,localhost,.internal expecting to prevent metadata and internal network access, axios < 1.7.4 still routes requests to those targets through the proxy (exposing credentials and data to proxy logs) or in some configurations through a direct connection that should have been blocked. Cloud workloads with IMDSv1 enabled are most at risk.


server/routes/proxy.js — axios < 1.7.4 (vulnerable) VULNERABLE: User-controlled URL passed to axios.get() without hostname validation // Common proxy route pattern found in many Node.js applications // The route accepts a user-controlled URL and forwards the request via axios app.get('/proxy', async (req, res) => { const { url } = req.query; // Validate: is the URL HTTPS? if (!url.startsWith('https://')) { return res.status(400).send('HTTPS required'); } // Problem: axios < 1.7.4 does not correctly check NO_PROXY for all hostname formats. // If NO_PROXY=169.254.169.254 is set, the intent is to bypass the proxy for metadata. // But axios's NO_PROXY parsing fails for certain hostname patterns, routing the request // through the proxy (leaking data in proxy logs) or mis-routing it. try { const response = await axios.get(url, { timeout: 3000, validateStatus: () => true, }); res.send(response.data); } catch (err) { res.status(502).send('Proxy error'); } }); // Attacker crafts: /proxy?url=https://169.254.169.254/latest/meta-data/ // axios < 1.7.4 bypasses the NO_PROXY exclusion and routes to cloud metadata
Attack: bypassing NO_PROXY with crafted hostname # Environment: NO_PROXY=169.254.169.254,localhost,.internal # Expected: requests to 169.254.169.254 bypass proxy (direct connection, no proxy logs) # Actual with axios < 1.7.4: NO_PROXY parsing bug causes the request to go through # the configured proxy, exposing IAM credentials in proxy access logs # Attacker payload (no authentication required): GET /proxy?url=https://169.254.169.254/latest/meta-data/iam/security-credentials/ # The NO_PROXY check in axios < 1.7.4: # - The hostname "169.254.169.254" should match the NO_PROXY entry "169.254.169.254" # - But the string parsing logic skips the check when the hostname looks like # an IP address in a URL with unusual formatting # - Result: request routes through proxy → credentials exposed in proxy logs

// commit — axios 1.7.4
lib/adapters/http.js — axios 1.7.4 fix Fixed in axios 1.7.4 — proper NO_PROXY parsing following WHATWG URL standard // BEFORE (vulnerable): // axios < 1.7.4 used a simple string split on NO_PROXY and did not handle: // - IPv4-mapped IPv6 addresses correctly // - Partial domain matches (*.example.com) // - Port-specific NO_PROXY entries (e.g. example.com:8080) // - Hostnames with leading/trailing whitespace or unusual formatting // AFTER (fixed): // axios 1.7.4 uses the WHATWG URL Standard's hostname parsing logic // to check NO_PROXY before routing any request. // NO_PROXY entries are now parsed as hostname-permission patterns per // the IANA Proxy-Related Environmental Variables spec. // Key change: strictNO_PROXY flag added to bypassHonorMultipleExceptionsInIdle, // axios combines the strict proxy flags and uses proper URL parsing // to determine whether a given hostname matches any NO_PROXY entry.

The fix replaces the ad-hoc NO_PROXY string parsing with a standards-compliant hostname matching function that correctly handles domain wildcards, IP addresses (including IPv4-mapped IPv6), port specifications, and unusual hostname encodings. This aligns axios's behavior with other HTTP clients (curl, wget, urllib) that handle NO_PROXY correctly.


🟠 PullLight — High Finding
[SSRF] Unguarded URL parameter passed to axios.get() — proxy route handler in server/routes/proxy.js

User-controlled url query parameter is passed directly to axios.get(url) without hostname validation. Any user can request arbitrary URLs through this proxy — including https://169.254.169.254/latest/meta-data/ (AWS/GCP metadata), http://localhost:5432 (internal database ports), or internal service URLs. Additionally, axios < 1.7.4 does not correctly honor NO_PROXY, so even environment-level proxy exclusions cannot be trusted as a mitigation. This is CWE-918 (SSRF). Upgrade axios to ≥ 1.7.4. As defense-in-depth, add a hostname allowlist and validate the resolved IP against a blocklist before making the request.
```suggestion // Fix Option 1: Upgrade axios >= 1.7.4 // Fix Option 2: Add hostname allowlist AND IP validation as defense-in-depth: const ALLOWED_HOSTS = new Set([ 'api.example.com', 'status.example.com', 'api.github.com', ]); function isAllowedHost(url) { try { const { hostname } = new URL(url); return ALLOWED_HOSTS.has(hostname); } catch { return false; } } if (!isAllowedHost(url)) return res.status(403).send('Blocked'); const resp = await axios.get(url, { timeout: 3000 }); // Defense-in-depth: also check the resolved IP const ip = await dns.lookup(urlHostname); if (RESERVED_IP_RANGES.has(ip)) return res.status(403).send('Reserved IP'); ```

// why this happened
The NO_PROXY environment variable is documented in the IANA Proxy-Related Environmental Variables spec, but its format is underspecified in practice. Many HTTP clients implement NO_PROXY parsing differently — some treat *.example.com as a wildcard suffix, some treat example.com:8080 as port-specific, some ignore IPv4 addresses in certain formats. axios's proxy implementation was written before the WHATWG URL Standard's proxy bypass rules were widely understood, so the hostname matching was implemented as a simple string contains check rather than a proper URL parsing + pattern match. The fix aligns axios's behavior with curl and urllib's implementation of NO_PROXY.

Three reasons this vulnerability hides from standard PR review

  • NO_PROXY is a deployment configuration, not code. Reviewers examine the proxy route handler and see a valid HTTPS check — they don't think about whether axios correctly honors the environment's proxy bypass list. The bug is in the library, not in the code being reviewed.
  • The SSRF requires two conditions that live in different layers. The attacker needs (1) a proxy route that accepts user URLs, and (2) a NO_PROXY entry that axios doesn't parse correctly. Neither condition looks dangerous in isolation — the route handler looks like a standard proxy pattern, and the NO_PROXY variable is set by infrastructure, not application code.
  • The impact requires knowing HTTP client proxy semantics. Understanding that axios < 1.7.4 incorrectly parses NO_PROXY requires reading the axios proxy-checks source code and understanding the WHATWG URL standard's proxy bypass rules. Most security reviewers would not catch this without specifically auditing axios version and its NO_PROXY implementation.

August 2024 GHSA-8hc4-8hv5 published; axios 1.7.4 released with corrected NO_PROXY parsing
August 2024 CVE-2024-39338 published on NVD (CVSS 8.7 High)
August–September 2024 Public blog posts by curl and urllib maintainers document the NO_PROXY parsing issue as a broader ecosystem problem affecting multiple HTTP clients
June 2026 PullLight documents CVE-2024-39338 as case study #21 — helps engineers recognize the proxy-route SSRF pattern and the importance of upgrading HTTP client libraries

Don't let this happen to your PRs

More Case Studies
8.6Next.js WebSocket SSRF (CVE-2026-44578)
8.2Spring Boot Actuator Auth Bypass (CVE-2026-22731)
8.1ip Package SSRF (CVE-2024-29415)
7.2Next.js Auth Bypass (CVE-2025-29927)