Back to Blog
critical SEVERITY8 min read

How Prototype Pollution happens in Node.js package managers and how to fix it

A critical prototype pollution vulnerability in loader-utils versions 1.4.0 and 2.0.2 allowed attackers to corrupt JavaScript object prototypes through specially crafted query parameters. The fix upgrades loader-utils to patched versions 1.4.1 and 2.0.4, which sanitize the parseQuery() function's handling of untrusted input and apply stricter dependency constraints.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 31, 2026•Reviewed August 31, 2026

Answer Summary

CVE-2022-37601 is a prototype pollution vulnerability in Node.js's loader-utils package that allows attackers to modify object prototypes through the parseQuery() function when processing untrusted input. The vulnerability was fixed by upgrading loader-utils from version 1.4.0 to 1.4.1 and from 2.0.2 to 2.0.4, which implement input validation in the query parsing logic and tighten dependency version constraints to prevent cascading vulnerabilities.

Vulnerability at a Glance

cweCWE-1321 (Improperly Controlled Modification of Object Prototype Attributes)
fixUpgrade loader-utils to versions 1.4.1+ or 2.0.4+ and constrain transitive dependencies with caret ranges
riskAttackers can corrupt object prototypes, potentially leading to code execution, privilege escalation, or denial of service
languageJavaScript/Node.js
root causeInsufficient input validation in loader-utils' parseQuery.js when processing query parameters
vulnerabilityPrototype Pollution in parseQuery()

How Prototype Pollution Happens in Node.js Package Managers and How to Fix It

Introduction

In a private Node.js application, the security scanning tool Trivy flagged a critical vulnerability in the dependency tree: CVE-2022-37601 affecting loader-utils versions 1.4.0 and 2.0.2. The issue wasn't in application code—it was hiding in a widely-used build utility that processes query strings and configuration parameters.

The vulnerability lies in the parseQuery.js file within loader-utils, specifically in how the parseQuery() function handles untrusted query parameters. Without proper validation, an attacker could craft a malicious query string that modifies JavaScript's Object.prototype, corrupting the prototype chain for all objects in the application's runtime. This is not a theoretical risk: prototype pollution has been weaponized in real-world attacks against Node.js applications.

This fix matters because loader-utils is a foundational package in the webpack ecosystem, meaning the vulnerability could affect any Node.js build pipeline, bundling process, or configuration loader that relies on it.


The Vulnerability Explained

What Is Prototype Pollution?

Prototype pollution is a vulnerability unique to dynamically-typed languages like JavaScript. Because JavaScript objects inherit properties from their prototypes, modifying Object.prototype affects every object in the entire application:

// Normal object behavior
const user = { name: "Alice", isAdmin: false };

// If an attacker pollutes Object.prototype:
// Object.prototype.isAdmin = true;

// Now ALL objects have isAdmin = true!
console.log(user.isAdmin);  // true — vulnerability!

The Vulnerable Code Pattern

In loader-utils@1.4.0 and 2.0.2, the parseQuery() function processes URL query strings without sufficient validation. The vulnerable pattern looks like this:

// Simplified vulnerable code from parseQuery.js
function parseQuery(query) {
  const obj = {};

  // Parsing query string without prototype guards
  query.split("&").forEach(pair => {
    const [key, value] = pair.split("=");
    obj[key] = decodeURIComponent(value);  // No validation!
  });

  return obj;
}

// An attacker could send:
// ?__proto__[isAdmin]=true
// ?constructor[prototype][isAdmin]=true

// This would modify Object.prototype!

The issue is that parseQuery() accepts keys like __proto__, constructor, and prototype without filtering, allowing attackers to access and modify the prototype chain.

Real-World Attack Scenario

Consider a webpack configuration loader that uses loader-utils to parse build parameters:

// vulnerable build config loader
const loaderUtils = require('loader-utils');
const config = {};

// Attacker-controlled query string from a build webhook
const params = "?__proto__[requireUnsafe]=true&output=bundle.js";
const parsed = loaderUtils.parseQuery(params);

// Merge into config
Object.assign(config, parsed);

// Now Object.prototype is polluted:
// All objects have requireUnsafe = true
// An isAdmin check anywhere becomes true: {} instanceof Admin bypassed

Impact on This Application

In the context of bun.lock (Bun package manager lock file), loader-utils handles dependency resolution and build parameter parsing. A prototype pollution attack could:

  1. Bypass security checks: isAdmin, isAuthenticated, or similar boolean flags become true for all objects
  2. Trigger unintended code paths: Configuration-driven logic could be manipulated
  3. Cause denial of service: Polluting critical properties could crash or destabilize the build process
  4. Enable privilege escalation: In monorepo or CI/CD contexts, attackers could elevate privileges across the build pipeline

The Fix

The fix addresses CVE-2022-37601 by upgrading loader-utils across both dependency branches in the lock file:

Change 1: Direct Dependency Update

- "loader-utils": ["loader-utils@1.4.0", "", { "dependencies": { "big.js": "5.2.2", "emojis-list": "3.0.0", "json5": "1.0.2" } }, "sha512-..."],
+ "loader-utils": ["loader-utils@1.4.1", "", { "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", "json5": "^1.0.1" } }, "sha512-..."],

What changed:
- Version: 1.4.0 → 1.4.1
- Transitive dependencies now use caret ranges (^) instead of fixed versions
- Hash updated to reflect the new package content

Change 2: Nested Dependency Update

- "file-loader/loader-utils": ["loader-utils@2.0.2", "", { "dependencies": { "big.js": "5.2.2", "emojis-list": "3.0.0", "json5": "2.2.3" } }, "sha512-..."],
+ "file-loader/loader-utils": ["loader-utils@2.0.4", "", { "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", "json5": "^2.1.2" } }, "sha512-..."],

What changed:
- Version: 2.0.2 → 2.0.4
- Dependency versions also updated with caret ranges
- Hash reflects the patched package

How the Patch Fixes the Issue

In loader-utils 1.4.1+ and 2.0.4+, the parseQuery() function now includes prototype pollution guards:

// Fixed version includes validation
function parseQuery(query) {
  const obj = {};

  query.split("&").forEach(pair => {
    const [key, value] = pair.split("=");

    // GUARD: Skip dangerous keys
    if (key === "__proto__" || 
        key === "constructor" || 
        key === "prototype") {
      return;  // Silently skip, or throw error
    }

    obj[key] = decodeURIComponent(value);
  });

  return obj;
}

Security improvements:
1. Allowlist validation: Only safe property names are processed
2. Prototype chain protection: __proto__, constructor, and prototype keys are rejected
3. Tightened transitive dependencies: The caret ranges ensure that security patches to json5 and other deps are automatically included
4. Updated hashes: Prevents downgrade attacks by ensuring exact package verification


Prevention & Best Practices

1. Never Trust User Input in Object Keys

When parsing query strings, URL parameters, or JSON payloads, validate key names against an allowlist:

const ALLOWED_KEYS = ["page", "limit", "sort", "filter"];
const params = parseQueryString(userInput);

// Validate each key
Object.keys(params).forEach(key => {
  if (!ALLOWED_KEYS.includes(key)) {
    throw new Error(`Invalid parameter: ${key}`);
  }
});

2. Use Secure Parsing Libraries

Instead of writing custom query parsers, use well-maintained libraries that handle prototype pollution:

// Good: Use built-in URL API with query parameter validation
const url = new URL(userInput);
const params = Object.fromEntries(url.searchParams);

// Good: Use qs library with options
const qs = require("qs");
const params = qs.parse(userInput, { 
  allowPrototypes: false  // Prevents prototype pollution
});

3. Freeze Critical Objects

Prevent prototype modifications on sensitive objects:

const criticalConfig = Object.freeze({
  apiKey: process.env.API_KEY,
  isAdmin: false,
  permissions: []
});

// Now Object.prototype.isAdmin = true won't affect criticalConfig

4. Keep Dependencies Updated

Use tools like npm audit, Snyk, or Trivy to detect and remediate vulnerable dependencies:

# Identify vulnerable packages
npm audit

# Fix with automated upgrades
npm audit fix

# Or use security scanning in CI/CD
trivy fs --severity CRITICAL .

5. Implement Property Validation

Check for dangerous property names during deserialization:

function safeDeserialize(obj) {
  const DANGEROUS_PROPS = ["__proto__", "constructor", "prototype"];

  for (const key of Object.keys(obj)) {
    if (DANGEROUS_PROPS.includes(key)) {
      delete obj[key];  // Remove dangerous properties
    }
  }

  return obj;
}

6. Use CWE-1321 Awareness Tools

Leverage static analysis to catch prototype pollution:

# Semgrep rule for prototype pollution
semgrep -r . --config p/cwe-1321

7. Monitor for Prototype Pollution at Runtime

In production, monitor for suspicious property assignments:

// Log when Object.prototype is modified
const handler = {
  set(target, prop, value) {
    if (prop === "__proto__" || prop === "constructor") {
      console.error(`Prototype pollution detected: ${prop}`);
      // Alert security team
    }
    return Reflect.set(target, prop, value);
  }
};

const proxiedObject = new Proxy({}, handler);

Key Takeaways

  • Prototype pollution in parseQuery() corrupted objects across the entire runtime: The unvalidated key handling in loader-utils 1.4.0 and 2.0.2 allowed attackers to modify Object.prototype through crafted query strings.

  • The fix validates and rejects dangerous property names: Versions 1.4.1 and 2.0.4 implement allowlist-based filtering to prevent __proto__, constructor, and prototype keys from polluting the prototype chain.

  • Dependency management matters for security: The upgrade also tightened transitive dependency constraints (json5, big.js, emojis-list) with caret ranges, ensuring security patches propagate automatically.

  • Lock file integrity is critical: The hash updates in bun.lock ensure that only the patched, verified versions are installed, preventing downgrade attacks.

  • This vulnerability affects build pipelines and configuration loaders: Anywhere loader-utils processes untrusted input (query strings, build parameters, CI/CD webhooks), prototype pollution could have been exploited.


How Orbis AppSec Detected This

Source: Dependency version in bun.lock file specifying vulnerable loader-utils versions (1.4.0 and 2.0.2)

Sink: The parseQuery() function in parseQuery.js within loader-utils, which processes URL query strings without prototype pollution guards

Missing control: No validation of property keys to block prototype chain access; missing allowlist filtering for dangerous keys like __proto__, constructor, and prototype

CWE: CWE-1321 (Improperly Controlled Modification of Object Prototype Attributes), related to CWE-94 (Improper Control of Generation of Code)

Fix: Upgraded loader-utils from 1.4.0 → 1.4.1 and 2.0.2 → 2.0.4 in bun.lock, which include prototype pollution guards in parseQuery() and tightened transitive dependency constraints

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 is a subtle but severe vulnerability that can compromise the integrity of an entire Node.js application. The CVE-2022-37601 vulnerability in loader-utils demonstrates how even foundational, widely-trusted packages can harbor dangerous flaws when they process untrusted input without proper safeguards.

By upgrading to patched versions (1.4.1 and 2.0.4), validating input keys against allowlists, and keeping dependencies current, development teams can eliminate this attack surface. The automated fix applied here—changing just two lines in bun.lock—shows how quickly prototype pollution can be remediated, but only if teams actively scan for and monitor vulnerable dependencies.

Remember: prototype pollution bypasses trust boundaries in your codebase. Treat any code that processes user-controlled property names as a potential attack vector, and always validate against known-safe patterns. Your application's security depends on it.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #10793

Related Articles

high

CVE-2026-4800: lodash Template Imports Allow Code Execution

CVE-2026-4800 affects lodash's `_.template()` templating API, where untrusted input reaching the `imports` option can lead to arbitrary code execution. The fix was shipped as a dependency upgrade from lodash 4.17.21 to 4.18.1 in the project's lockfile, though the PR itself notes it was never verified against the actual code paths in this repository.

critical

proxy-addr 2.0.7 IP Spoofing: CVE-2026-90711 Trust Bypass

A critical vulnerability in proxy-addr 2.0.7 allowed attackers to spoof client IP addresses by manipulating X-Forwarded-For headers when the trust chain evaluation contained specific misconfigurations. The fix in version 2.0.8 hardens the trust evaluation logic to prevent IP address falsification in Express.js applications relying on this common middleware dependency.

high

TweenMax `_applyCycle` Prototype Pollution via vars.cycle Keys

A bundled copy of the TweenMax animation library copied attacker-influenceable `vars.cycle` property names straight onto a tween configuration object using an unguarded `for...in` loop, so a key named `__proto__`, `constructor`, or `prototype` was written through to the object's prototype chain. The fix adds an explicit key denylist to both copies of the `_applyCycle` helper so those three names are skipped during the merge. No CVE or GHSA is assigned; the issue is tracked as CWE-1321 (Improperl

high

picomatch 2.3.1 ReDoS: Extglob Pattern Catastrophic Backtracking

picomatch versions below 2.3.2, 3.0.2, or 4.0.4 contain a Regular Expression Denial of Service vulnerability in extglob pattern parsing. An attacker can cause catastrophic backtracking with patterns containing nested alternations and quantifiers, freezing any Node.js process that evaluates untrusted glob expressions.

critical

pet-window.js Dynamic Code Evaluation: CWE-94 Hardening via Number

The pet-window module constructed dynamic JavaScript by embedding raw configuration values into code strings. An attacker with local access could inject arbitrary JavaScript by modifying stored configuration. The fix replaces string interpolation with explicit Number() coercion and NaN validation for all numeric configuration parameters.

high

sanitizeUnicodeInput(): Fullwidth U+ Bypasses Codepoint Validation

The `sanitizeUnicodeInput()` helper used by the project character-range settings screen rewrote `U+` prefixes to `0x` and called `parseInt()`, but never normalized its argument first. Compatibility-equivalent forms such as fullwidth `U+`, superscript digits, or mathematical alphanumerics never matched the `/U\+/gi` regex, fell through to the `else return inputString` branch, and were handed back to callers verbatim as "sanitized" values. The fix inserts a `String.prototype.normalize('NFKC')` pas