Résumé
Axios Node HTTP adapter can use an inherited proxy after interceptor config cloning
Détails de l’avis
Summary
Axios’ Node.js HTTP adapter can route requests through an attacker-controlled proxy when Object.prototype.proxy is polluted and request configuration is materialized as a regular object before dispatch.
Recent axios releases harden merged request config by creating a null-prototype object. However, request interceptors run after that merge and may return a replacement config. A common immutable interceptor pattern such as {...config} or Object.assign({}, config) converts the hardened config back into a normal object. Axios then dispatches that object without re-hardening it, and the Node HTTP adapter reads config.proxy through the prototype chain.
Impact
In a Node.js deployment using the HTTP adapter, an attacker who can trigger prototype pollution elsewhere in the process can route affected HTTP requests through an attacker-controlled proxy.
The highest confirmed impact is for plaintext HTTP requests. The proxy can observe explicit Authorization headers, axios-generated Basic auth from config.auth, request method, absolute URL, Host, and request body content. The proxy can also return its own response to axios for the affected request.
This does not establish browser impact. It also does not establish HTTPS header or body disclosure under normal TLS validation.
Affected Functionality
Affected functionality is limited to axios requests that use the Node.js HTTP adapter, including default Node usage when the HTTP adapter is selected and explicit adapter: 'http' usage.
The relevant configuration path is config.proxy in the Node HTTP adapter. The hardened-bypass path requires a request interceptor such as:
api.interceptors.request.use((config) => ({
...config,
headers: {
...config.headers,
'X-App': 'demo'
}
}));
Unaffected or mitigating conditions include browser adapters, the Node fetch adapter, no polluted Object.prototype.proxy, an own proxy: false or safe own proxy value on the config, and hardened releases where interceptors return the original null-prototype config instead of a regular object clone.
Technical Details
lib/core/mergeConfig.js creates a null-prototype merged config and uses own-property reads for merged values. This is intended to prevent polluted Object.prototype values from affecting config behavior.
lib/core/Axios.js runs request interceptors after the merge. In both the asynchronous and synchronous interceptor paths, axios passes the interceptor-returned config into dispatch.
lib/core/dispatchRequest.js accepts that returned config, transforms request data, selects the adapter, and calls the adapter without re-hardening or re-normalizing the config.
lib/adapters/http.js uses own-property reads for several sensitive fields, but the initial proxy dispatch path still passes config.proxy directly into setProxy(). If an interceptor returned a regular object, config.proxy can resolve to inherited Object.prototype.proxy.
Proof of Concept of Attack
import axios from './index.js';
import http from 'node:http';
for (const key of [
'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY',
'http_proxy', 'https_proxy', 'all_proxy',
'NO_PROXY', 'no_proxy'
]) {
delete process.env[key];
}
const listen = (handler) => new Promise((resolve, reject) => {
const server = http.createServer(handler);
server.once('error', reject);
server.listen(0, '127.0.0.1', () => resolve(server));
});
const close = (server) => new Promise((resolve) => server.close(resolve));
const targetHits = [];
const proxyHits = [];
const target = await listen((req, res) => {
targetHits.push(req.url);
res.end('target');
});
const proxy = await listen((req, res) => {
let body = '';
req.on('data', (chunk) => body += chunk);
req.on('end', () => {
proxyHits.push({
url: req.url,
authorization: req.headers.authorization,
host: req.headers.host,
body
});
res.setHeader('content-type', 'application/json');
res.end('{"server":"proxy"}');
});
});
Object.prototype.proxy = {
protocol: 'http',
host: '127.0.0.1',
port: proxy.address().port
};
const api = axios.create();
api.interceptors.request.use((config) => ({
...config,
headers: {
...config.headers,
'X-App': 'demo'
}
}));
try {
const url = `http://127.0.0.1:${target.address().port}/api/secret`;
const res = await api.post(
url,
{secret: 'request-body-secret'},
{headers: {Authorization: 'Bearer EXPLICIT_SECRET'}}
);
console.log({
response: res.data,
targetHits,
proxyHits,
finalConfigHasOwnProxy: Object.hasOwn(res.config, 'proxy')
});
} finally {
delete Object.prototype.proxy;
await close(target);
await close(proxy);
}
Expected vulnerable result: the response comes from the proxy, targetHits is empty, and proxyHits contains the absolute URL, authorization header, host header, and request body.
Workarounds
Set an own proxy: false on affected requests or on an axios instance when proxy support is not required.
Avoid request interceptors that return regular object clones of config in hardened releases. Returning the original config or cloning into a null-prototype object avoids this specific bypass, but this is fragile and should not replace a fix.
Use the Node fetch adapter for affected requests where its behavior is compatible with the application.
Original Report
Summary
Axios hardens merged request config by creating a null-prototype object, preventing polluted Object.prototype properties from influencing request behavior. Request interceptors run after that hardening, and a normal immutable interceptor pattern such as {...config} or Object.assign({}, config) re-materializes the config as a regular object. Axios then dispatches that interceptor-returned object without re-hardening it. In the Node HTTP adapter, config.proxy is read through the prototype chain, allowing a polluted Object.prototype.proxy to route authenticated HTTP requests through an attacker-controlled proxy.
Impact
In a Node.js deployment using the HTTP adapter, an attacker who can trigger prototype pollution elsewhere in the process can cause affected axios requests to be sent through an attacker-controlled proxy when the application uses a request interceptor that returns a plain object copy of the config.
Verified local impact:
- Authenticated request redirection to attacker-controlled proxy.
- Disclosure of explicit Authorization headers.
- Disclosure of axios-generated Basic auth headers from config.auth.
- Disclosure of request metadata: method, absolute URL, Host header.
- Disclosure of POST body content.
This report does not claim browser impact or proven HTTPS credential disclosure. The demonstrated credential and body disclosure is for Node HTTP-adapter requests over HTTP/plaintext.
Affected component
The affected component is the Node.js HTTP adapter request path after request interceptors have run.
The issue requires:
- Node.js HTTP adapter usage.
- A polluted Object.prototype.proxy.
- A request interceptor that returns a plain object copy of the config.
- No own proxy: false or safe own proxy property on the request config.
Affected versions
Confirmed affected for this specific hardening-bypass variant:
axios@1.16.0 was the latest published version observed via npm view axios version during validation.
Related older behavior observed during testing:
- 1.13.0, 1.13.6, 1.14.0, 1.15.0, and 1.15.1 routed via inherited Object.prototype.proxy even without the interceptor re-materialization step. That is related background, not the narrowed hardening-bypass variant described here.
Root cause
Initial hardening
Axios initially hardens merged request config by creating a null-prototype object in mergeConfig(), which is meant to prevent inherited Object.prototype
Références
- https://github.com/advisories/GHSA-gcfj-64vw-6mp9
- https://github.com/axios/axios/security/advisories/GHSA-gcfj-64vw-6mp9
- https://github.com/axios/axios/pull/11000
- https://github.com/axios/axios/pull/11001
- https://github.com/axios/axios/commit/1417285c69344bbcc6420a021f67dee0c6fedb2d
- https://github.com/axios/axios/commit/32fc489632377d214db55bfa4e2c48486a7d7ce2
- https://github.com/axios/axios/releases/tag/v0.33.0
- https://github.com/axios/axios/releases/tag/v1.18.0
Vulnérabilités liées
Tout Supply chain →- HIGHCVE-2026-75912
CodeWhale: Argument Injection in `git_blame` Tool Allows Arbitrary File Read Without Approval
- HIGHCVE-2026-75915
CodeWhale: js_execution leaks parent environment to model context via missing env scrub
- HIGHCVE-2026-75859
CodeWhale: Project config `instructions` override enables arbitrary file read into AI system prompt via cloned repository
- HIGHCVE-2026-72804
SiYuan: Graph endpoints omit the publish-password tier: anonymous readers receive block-level content of password-protected documents
- HIGHCVE-2026-63376
toml-node: Prototype Pollution Leads to `Object.prototype` Corruption via `__proto__` Key-Path Desynchronization
- HIGHCVE-2026-82404
TOON: Prototype pollution when decoding untrusted TOON input