GitHub Security Advisory: GHSA-3p68-rc4w-qgx5
Package: axios (NPM)
Severity: MODERATE (CVSS 4.8)
CWE: CWE-441: Unintended Proxy or Intermediary ('Confused Deputy'), CWE-918: Server-Side Request Forgery (SSRF)
Vulnerable versions: >= 1.0.0, < 1.15.0
Patched in: 1.15.0

Summary: Axios has a NO_PROXY Hostname Normalization Bypass that Leads to SSRF

Details:
Axios does not correctly handle hostname normalization when checking `NO_PROXY` rules.
Requests to loopback addresses like `localhost.` (with a trailing dot) or `[::1]` (IPv6 literal) skip `NO_PROXY` matching and go through the configured proxy.

This goes against what developers expect and lets attackers force requests through a proxy, even if `NO_PROXY` is set up to protect loopback or internal services.

According to [RFC 1034 §3.1](https://datatracker.ietf.org/doc/html/rfc1034#section-3.1) and [RFC 3986 §3.2.2](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2), a hostname can have a trailing dot to show it is a fully qualified domain name (FQDN). At the DNS level, `localhost.` is the same as `localhost`. 
However, Axios does a literal string comparison instead of normalizing hostnames before checking `NO_PROXY`. This causes requests like `http://localhost.:8080/` and `http://[::1]:8080/` to be incorrectly proxied.

This issue leads to the possibility of proxy bypass and SSRF vulnerabilities allowing attackers to reach sensitive loopback or internal services despite the configured protections.

---

**PoC**

```js
import http from "http";
import axios from "axios";

const proxyPort = 5300;

http.createServer((req, res) => {
  console.log("[PROXY] Got:", req.method, req.url, "Host:", req.headers.host);
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("proxied");
}).listen(proxyPort, () => console.log("Proxy", proxyPort));

process.env.HTTP_PROXY = `http://127.0.0.1:${proxyPort}`;
process.env.NO_PROXY = "localhost,127.0.0.1,::1";

async function test(url) {
  try {
    await axios.get(url, { timeout: 2000 });
  } catch {}
}

setTimeout(async () => {
  console.log("\n[*] Testing http://localhost.:8080/");
  await test("http://localhost.:8080/"); // goes through proxy

  console.log("\n[*] Testing http://[::1]:8080/");
  await test("http://[::1]:8080/"); // goes through proxy
}, 500);
```

**Expected:** Requests bypass the proxy (direct to loopback).
**Actual:** Proxy logs requests for `localhost.` and `[::1]`.

---

**Impact**

* Applications that rely on `NO_PROXY=localhost,127.0.0.1,::1` for protecting loopback/internal access are vulnerable.
* Attackers controlling request URLs can:

  * Force Axios to send local traffic through an attacker-controlled proxy.
  * Bypass SSRF mitigations relying on NO\_PROXY rules.
  * Potentially exfiltrate sensitive responses from internal services via the proxy.
  
  
---

**Affected Versions**

* Confirmed on Axios **1.12.2** (latest at time of testing).
* affects all versions that rely on Axios’ current `NO_PROXY` evaluation.

---

**Remediation**
Axios should normalize hostnames before evaluating `NO_PROXY`, including:

* Strip trailing dots from hostnames (per RFC 3986).
* Normalize IPv6 literals by removing brackets for matching.