How Prototype Pollution Denial of Service Happens in Node.js HTTP Libraries and How to Fix It
Introduction
In a recent security scan, a critical prototype pollution vulnerability was discovered in axios 1.12.0 (CVE-2026-25639), a widely-used HTTP client library for Node.js. The vulnerability exists in the mergeConfig function within axios's configuration handling logic, which unsafely merges user-supplied configuration objects without properly sanitizing prototype pollution vectors.
The specific issue: when axios merges HTTP request configurations, it directly assigns properties from user-controlled objects without checking for dangerous keys like __proto__. An attacker can craft a malicious HTTP request with a specially-crafted configuration object containing a __proto__ key, which would poison the JavaScript global object prototype. This could cause the entire application to malfunction, resulting in a denial of service condition.
This matters because axios is used in thousands of Node.js applications for making HTTP requests. Any application using axios ≤1.12.0 to handle HTTP requests with user-influenced configuration could be vulnerable to remote denial of service attacks.
The Vulnerability Explained
What is Prototype Pollution?
Prototype pollution is a JavaScript-specific vulnerability that exploits the way JavaScript handles object inheritance through prototypes. In JavaScript, objects inherit properties from their prototype chain. By manipulating the __proto__ property or the prototype property of constructors, an attacker can inject malicious properties into the global prototype object, affecting all objects in the application.
The Vulnerable Code Pattern
The vulnerability in axios 1.12.0 exists in how the library merges configuration objects. When a user makes an HTTP request with custom configuration, axios internally calls mergeConfig() to combine the default configuration with user-supplied options:
// Vulnerable pattern in axios 1.12.0
function mergeConfig(config1, config2) {
config2 = config2 || {};
const result = {};
// Unsafe iteration over all properties
for (const key in config2) {
if (config2.hasOwnProperty(key)) {
result[key] = config2[key]; // Directly assigns without sanitization
}
}
return result;
}
The problem: when config2 contains a __proto__ key, the assignment result[key] = config2[key] actually modifies the prototype chain, not just the result object.
How the Attack Works
An attacker can craft a malicious HTTP request that exploits this:
// Attacker-controlled payload
const maliciousConfig = {
"__proto__": {
"timeout": "invalid",
"responseType": "corrupted"
}
};
// When axios merges this config
axios.request({
url: 'https://api.example.com/data',
...maliciousConfig // Spreads the malicious config
});
// The global Object.prototype is now poisoned:
// Object.prototype.timeout === "invalid"
// Object.prototype.responseType === "corrupted"
Once the prototype is poisoned, every object in the application inherits these malicious properties, causing:
- Configuration corruption: legitimate code expecting specific types receives corrupted values
- Application crashes: downstream code fails when accessing poisoned properties
- Denial of Service: the entire application becomes unstable or unusable
Real-World Impact
For an application using axios to fetch data from user-controlled sources or APIs, an attacker could:
- Intercept or control an HTTP response
- Include a malicious
__proto__payload in the response data - When axios processes this response with its mergeConfig function, the prototype is poisoned
- The application crashes or becomes unresponsive
- All users of the application experience a denial of service
The Fix
The vulnerability was fixed by upgrading axios from version 1.12.0 to 1.13.5. This update includes a critical security patch in the mergeConfig function that implements strict input validation.
Changes Made
The package.json and package-lock.json were updated to reflect the new versions:
- "axios": "^1.12.0",
+ "axios": "^1.13.5",
Additionally, related dependencies were updated to versions that include complementary security fixes:
- "follow-redirects": "^1.15.6",
+ "follow-redirects": "^1.15.11",
- "form-data": "^4.0.4",
+ "form-data": "^4.0.5",
How the Fix Works
The patched version of axios (1.13.5) implements a secure object merging algorithm that:
- Rejects prototype-related keys: The mergeConfig function now explicitly checks for and rejects assignments to
__proto__,constructor, andprototype - Uses safe property assignment: Instead of direct assignment, it uses
Object.defineProperty()or similar mechanisms that prevent prototype chain pollution - Validates configuration structure: The function validates that configuration objects conform to expected schemas
The fixed mergeConfig function likely resembles:
// Fixed pattern in axios 1.13.5
function mergeConfig(config1, config2) {
config2 = config2 || {};
const result = {};
// Safe iteration that rejects dangerous keys
for (const key in config2) {
if (config2.hasOwnProperty(key)) {
// Explicitly reject prototype pollution vectors
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // Skip dangerous keys
}
result[key] = config2[key];
}
}
return result;
}
Why Both Dependencies Were Updated
- axios 1.13.5: Contains the core prototype pollution fix in mergeConfig
- follow-redirects 1.15.11: Updates to this transitive dependency ensure consistent security posture across the HTTP handling pipeline
- form-data 1.0.5: Updates to form data handling prevent similar prototype pollution attacks in multipart request handling
Prevention & Best Practices
1. Always Sanitize Prototype-Related Keys
When merging objects from untrusted sources, explicitly reject dangerous keys:
function safeObjectMerge(target, source) {
const dangerousKeys = ['__proto__', 'constructor', 'prototype'];
for (const key of Object.keys(source)) {
if (!dangerousKeys.includes(key)) {
target[key] = source[key];
}
}
return target;
}
2. Use Object.create(null) for Configuration Objects
Create configuration objects without a prototype to prevent prototype pollution:
// Configuration object with no prototype chain
const config = Object.create(null);
config.timeout = 5000;
config.retries = 3;
// Now __proto__ assignments won't affect the global prototype
3. Keep Dependencies Updated
Regularly update dependencies, especially HTTP clients and utility libraries. Use tools like npm audit and npm outdated to track vulnerable packages:
npm audit fix # Automatically fix vulnerable dependencies
npm outdated # Check for available updates
4. Use Security Scanning Tools
Implement automated security scanning in your CI/CD pipeline:
# Scan for known vulnerabilities
trivy fs .
npm audit
# Use Semgrep for prototype pollution patterns
semgrep --config=p/security-audit .
5. Validate Configuration Structure
Implement schema validation for configuration objects:
const Joi = require('joi');
const configSchema = Joi.object({
timeout: Joi.number().positive(),
retries: Joi.number().min(0),
headers: Joi.object().unknown(true),
// Explicitly reject dangerous keys
}).unknown(false);
const { error, value } = configSchema.validate(userConfig);
if (error) {
throw new Error('Invalid configuration');
}
Key Takeaways
-
Prototype pollution via
__proto__in axios 1.12.0's mergeConfig function: The vulnerability allowed attackers to poison the global object prototype by injecting a__proto__key into HTTP configuration objects, causing denial of service. -
Direct property assignment without sanitization was the root cause: The vulnerable code didn't check for or reject prototype-related keys before merging user-supplied configuration.
-
Upgrading to axios 1.13.5 implements strict input validation: The patched version explicitly rejects
__proto__,constructor, andprototypekeys in the mergeConfig function. -
Related dependencies must be updated together: follow-redirects and form-data were also updated to ensure consistent security across the HTTP handling pipeline and prevent similar attacks.
-
Prototype pollution is preventable with explicit key validation: Always reject dangerous keys when merging objects from untrusted sources, use
Object.create(null)for configuration objects, and implement schema validation.
How Orbis AppSec Detected This
Source: HTTP request configuration objects passed to axios (user-influenced data from API responses or external sources)
Sink: The mergeConfig function in axios 1.12.0 which directly assigns properties without sanitization
Missing control: No validation or sanitization of prototype-related keys (__proto__, constructor, prototype) before object property assignment
CWE: CWE-1321 (Improperly Controlled Modification of Object Prototype Attributes)
Fix: Upgrade axios to 1.13.5, which implements strict input validation in mergeConfig that rejects prototype pollution vectors
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
Prototype pollution vulnerabilities like CVE-2026-25639 demonstrate the importance of secure object handling in JavaScript applications. The axios library's oversight in not sanitizing the __proto__ key during configuration merging could have allowed attackers to cause widespread denial of service across thousands of applications.
The fix is straightforward: upgrade to axios 1.13.5 and related dependencies. However, the broader lesson is that developers must always be aware of JavaScript's prototype chain and implement explicit protections when merging objects from untrusted sources.
By following the prevention practices outlined above—sanitizing dangerous keys, using Object.create(null), validating configuration schemas, and keeping dependencies updated—you can protect your applications from prototype pollution attacks. Make security scanning a regular part of your development workflow, and always prioritize updates that address prototype pollution and similar prototype chain vulnerabilities.