Back to Blog
critical SEVERITY7 min read

How Prototype Pollution Denial of Service Happens in Node.js HTTP Libraries and How to Fix It

A critical prototype pollution vulnerability in axios versions 1.12.0 and earlier could allow attackers to trigger denial of service attacks by poisoning the configuration object through the `__proto__` key. The vulnerability was fixed by upgrading axios to 1.13.5 and updating related dependencies like follow-redirects to 1.16.0, which implements stricter input validation in the mergeConfig function.

O
By Orbis AppSec
•Published August 10, 2026•Reviewed August 10, 2026

Answer Summary

CVE-2026-25639 is a prototype pollution vulnerability in Node.js HTTP library axios (versions ≤1.12.0) that allows attackers to cause denial of service by injecting malicious `__proto__` keys into HTTP request configurations. The vulnerability exists in the `mergeConfig` function which unsafely merges user-supplied configuration objects without sanitizing prototype pollution vectors. The fix involves upgrading axios from 1.12.0 to 1.13.5, which implements proper object merging that rejects or sanitizes `__proto__` keys and similar prototype pollution payloads.

Vulnerability at a Glance

cweCWE-1321 (Improperly Controlled Modification of Object Prototype Attributes)
fixUpgrade axios to 1.13.5 which implements strict input validation and rejects prototype pollution vectors in configuration merging
riskAttackers can trigger denial of service by poisoning the global object prototype through HTTP configuration merging
languageJavaScript/Node.js
root causeThe axios mergeConfig function unsafely merges untrusted configuration objects without sanitizing __proto__ keys
vulnerabilityPrototype Pollution Denial of Service (CVE-2026-25639)

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:

  1. Intercept or control an HTTP response
  2. Include a malicious __proto__ payload in the response data
  3. When axios processes this response with its mergeConfig function, the prototype is poisoned
  4. The application crashes or becomes unresponsive
  5. 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:

  1. Rejects prototype-related keys: The mergeConfig function now explicitly checks for and rejects assignments to __proto__, constructor, and prototype
  2. Uses safe property assignment: Instead of direct assignment, it uses Object.defineProperty() or similar mechanisms that prevent prototype chain pollution
  3. 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, and prototype keys 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.

References

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #7

Related Articles

high

js-yaml 4.3.1 Denial of Service: Malformed Input Hangs YAML Parser

A denial of service vulnerability in js-yaml versions 4.3.1 and earlier allows attackers to hang the YAML parser indefinitely by providing specially crafted malformed input. The fix, released in versions 4.3.2 and 3.15.2, patches the parsing logic to prevent unbounded processing. Upgrading is recommended for all applications parsing untrusted YAML data.

high

Express `app.get('*')` Wildcard Handler Path Traversal in watch.js

A first-party Express server's wildcard route handler used `req.url.indexOf('font.woff2')` to gate access to a font file, allowing attackers to bypass the substring check with crafted paths. The fix replaces the catch-all handler with explicit route registration.

critical

Updater.parseUpdate() CWE-494: Unsigned Metadata Download

The parseUpdate function in the Updater component extracted download URLs from remote server responses without cryptographic verification, enabling supply chain attacks via compromised or spoofed update servers. The fix adds strict URL validation requiring HTTPS and a trusted hostname before accepting any update metadata.

high

brace-expansion DoS: Exponential Backtracking in Nested Brace Patterns

A critical vulnerability in brace-expansion allows attackers to cause denial of service by submitting specially crafted patterns with nested braces. The exponential-time complexity in pattern expansion creates a computationally expensive path that can freeze applications processing user-controlled input.

high

CVE-2026-67213: nanoid customAlphabet Infinite Loop Fix

nanoid, a widely-used ID generator pulled in transitively through postcss and vitepress, had an infinite-loop bug in its `customAlphabet` code path before version 5.1.6. This PR pins the entire dependency tree to nanoid 5.1.16 via a pnpm override so no transitive consumer can resolve back to the vulnerable 3.3.16 release.

high

KNX Project Extractor ZIP Bomb: Unbounded Decompression Before Size

The KNX project extractor used `@zip.js/zip.js` to decompress .knxproj files without enforcing maximum entry sizes, total archive sizes, or compression ratios. This allowed attackers to upload ZIP bombs that expanded exponentially—like the famous 42.zip producing 4.5PB from 42KB—consuming all available memory before the existing `Checked` validation could trigger. The fix introduces three hard limits: 512MB per entry, 1GB total per archive, and a 100:1 compression ratio ceiling.