Back to Blog
critical SEVERITY9 min read

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

CVE-2023-36665 is a critical prototype pollution vulnerability in protobufjs that allows attackers to corrupt JavaScript's Object prototype by crafting malicious protobuf messages. The vulnerability existed in protobufjs 6.11.3 and was resolved by upgrading to 6.11.4 (and 7.2.5 for the v7 branch). Applications that parse user-supplied protobuf data are directly at risk of runtime behavior manipulation, privilege escalation, or denial of service.

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

CVE-2023-36665 is a critical prototype pollution vulnerability (CWE-1321) in the Node.js library protobufjs, affecting versions prior to 6.11.4 and 7.2.5. An attacker can craft a protobuf message containing a specially constructed field name such as `__proto__` or `constructor` to overwrite properties on JavaScript's global `Object.prototype`, potentially enabling privilege escalation, property injection, or denial of service. The fix is to upgrade protobufjs from 6.11.3 to 6.11.4 (or from any v7 release to 7.2.5), which adds sanitization of user-controlled field names during message deserialization to prevent prototype chain manipulation.

Vulnerability at a Glance

cweCWE-1321
fixUpgrade protobufjs from 6.11.3 to 6.11.4 in package.json and package-lock.json, which adds field name validation to block prototype-polluting keys
riskAttackers can overwrite Object.prototype properties via crafted protobuf messages, enabling privilege escalation, property injection, or DoS
languageJavaScript / Node.js
root causeprotobufjs 6.11.3 did not sanitize user-controlled field names during message deserialization, allowing keys like `__proto__` to traverse the prototype chain
vulnerabilityPrototype Pollution

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


Vulnerability at a Glance

Field Detail
Vulnerability Prototype Pollution
CVE CVE-2023-36665
CWE CWE-1321 — Improperly Controlled Modification of Object Prototype Attributes
Severity Critical
Affected Package protobufjs 6.11.3 (and < 7.2.5 in the v7 branch)
Fixed In protobufjs 6.11.4 / 7.2.5
Language JavaScript / Node.js

Introduction

The package-lock.json in this project pinned protobufjs to version 6.11.3 — a version that contains a critical prototype pollution vulnerability. Any code path that deserializes a user-supplied protobuf message using this library becomes an entry point for an attacker to silently overwrite properties on Object.prototype, the root of JavaScript's object inheritance chain. Because prototype properties are inherited by every object in a Node.js process, a single malicious message can alter the behavior of unrelated parts of the application without any obvious error being thrown.

The vulnerability was assigned CVE-2023-36665 and rated Critical. It was detected by Trivy scanning the project's package-lock.json dependency tree.


The Vulnerability Explained

What Is Prototype Pollution?

In JavaScript, every object inherits properties from Object.prototype. If an attacker can cause your code to execute something equivalent to:

obj["__proto__"]["isAdmin"] = true;

…then every object in the running process suddenly has isAdmin === true, because they all share the same prototype. This is prototype pollution.

How protobufjs 6.11.3 Was Vulnerable

Protocol Buffers (protobuf) is a binary serialization format developed by Google. The protobufjs library parses .proto schema definitions and decodes binary or JSON-encoded protobuf messages into JavaScript objects. During decoding, the library maps protobuf field names to JavaScript object keys.

In protobufjs versions before 6.11.4, the deserialization logic did not adequately guard against field names that are special in JavaScript's prototype chain — specifically keys like:

  • __proto__
  • constructor
  • prototype

If an attacker crafts a protobuf message (or a JSON-encoded protobuf payload) containing one of these reserved names as a field, the library would process it and assign the attacker-controlled value directly onto the target object using bracket notation — effectively walking up the prototype chain rather than setting an own property.

A simplified illustration of the dangerous pattern inside the library's object-building logic (before the fix):

// Vulnerable pattern — no key sanitization
function setProperty(obj, key, value) {
  obj[key] = value; // If key is "__proto__", this pollutes Object.prototype
}

When key is "__proto__" and value is { "isAdmin": true }, the assignment obj["__proto__"] = { "isAdmin": true } modifies the prototype of obj, not a property named __proto__. In V8 (Node.js's JavaScript engine), this propagates to Object.prototype itself.

Concrete Attack Scenario

Consider an application that:
1. Accepts binary or JSON protobuf messages from users over an HTTP API.
2. Uses protobufjs 6.11.3 to decode those messages.
3. Later checks if (request.user.isAdmin) to gate privileged operations.

An attacker sends a crafted protobuf message with a field named __proto__ containing { "isAdmin": true }. After protobufjs decodes this message, Object.prototype.isAdmin is now true for the entire process lifetime. Every subsequent request.user.isAdmin check — even for unauthenticated users — returns true, granting full administrative access.

Beyond privilege escalation, prototype pollution can be chained with other vulnerabilities (e.g., property injection into child_process.spawn option objects) to achieve Remote Code Execution.

What Was Pinned in This Project

The vulnerable version was declared in two places in package-lock.json and package.json:

// package.json — before fix
"protobufjs": "^6.11.2"
// package-lock.json — node_modules/protobufjs — before fix
"version": "6.11.3",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz",
"integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg=="

Because the semver range was ^6.11.2 (which resolves to >=6.11.2 <7.0.0), the lock file had resolved to 6.11.3 — the vulnerable release. The range would allow 6.11.4, but the lock file pinned the older version until explicitly updated.


The Fix

The fix upgrades protobufjs from 6.11.3 to 6.11.4 in both package.json and package-lock.json. The 6.11.4 release adds sanitization of user-controlled field names during protobuf message deserialization, rejecting or neutralizing keys that would traverse the prototype chain.

Before and After: package.json

- "protobufjs": "^6.11.2",
+ "protobufjs": "^6.11.4",

Bumping the lower bound of the range to ^6.11.4 ensures that npm install will never again resolve to a vulnerable patch version within the 6.x series.

Before and After: package-lock.json (node_modules/protobufjs)

- "version": "6.11.3",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz",
- "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==",
+ "version": "6.11.4",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz",
+ "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==",
  "hasInstallScript": true,
+ "license": "BSD-3-Clause",

The integrity hash change is critical — it confirms that npm will now verify the downloaded tarball against the hash of the patched release, not the vulnerable one. Notably, 6.11.4 also formally declares its BSD-3-Clause license in the lock file metadata, a minor housekeeping improvement included in that release.

The same version bump appears in the flattened dependencies section of package-lock.json (the legacy npm v2 format section), ensuring consistency across both lock file representations:

  "protobufjs": {
-   "version": "6.11.3",
-   "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz",
-   "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==",
+   "version": "6.11.4",
+   "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz",
+   "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==",

Why Both Files Must Be Updated

Many developers assume that changing package.json alone is sufficient. It is not. The package-lock.json is the authoritative source of truth for npm ci (used in CI/CD pipelines). If only package.json is updated, a npm ci run in a deployment pipeline will still install 6.11.3 because it reads the exact version from the lock file. Updating both files is mandatory to guarantee the fix takes effect in all environments.


Prevention & Best Practices

1. Treat Deserialized Data as Untrusted Input

Any data arriving from outside your process boundary — HTTP bodies, WebSocket frames, message queue payloads — must be treated as potentially adversarial. This is especially true for rich deserialization formats like Protocol Buffers, which can encode complex nested structures.

2. Sanitize Keys Before Dynamic Property Assignment

When you must assign user-controlled keys to objects, guard against prototype-polluting names:

const DISALLOWED_KEYS = new Set(["__proto__", "constructor", "prototype"]);

function safeSet(obj, key, value) {
  if (DISALLOWED_KEYS.has(key)) {
    throw new Error(`Rejected potentially dangerous key: ${key}`);
  }
  obj[key] = value;
}

Or use Object.create(null) to create property bags with no prototype:

const safeMap = Object.create(null);
safeMap[userKey] = userValue; // No prototype to pollute

3. Lock and Audit Your Dependency Tree Regularly

The vulnerability in this project existed because the lock file pinned 6.11.3 while a patched 6.11.4 was available. Integrate dependency scanning into your CI pipeline:

# Audit with npm
npm audit

# Scan with Trivy (as used in this project)
trivy fs --scanners vuln package-lock.json

4. Use Object.freeze(Object.prototype) in Security-Sensitive Contexts

For applications where prototype pollution would be catastrophic, you can freeze the prototype at startup:

// Add to your application entry point
Object.freeze(Object.prototype);
Object.freeze(Object);

This causes prototype pollution attempts to fail silently (or throw in strict mode) rather than succeeding. Note that some third-party libraries may break if they legitimately extend Object.prototype.

5. Keep Semver Lower Bounds Up to Date

The original range "^6.11.2" allowed vulnerable versions. After a security fix, update the lower bound of your range to exclude all vulnerable versions:

// Before: allows 6.11.2, 6.11.3 (vulnerable)
"protobufjs": "^6.11.2"

// After: minimum is the patched version
"protobufjs": "^6.11.4"

Security Standards Reference

  • OWASP A08:2021 — Software and Data Integrity Failures covers dependency vulnerabilities like this one.
  • CWE-1321 — Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') is the authoritative classification.
  • OWASP Dependency-Check and npm audit are the recommended first-line tools for Node.js projects.

Key Takeaways

  • protobufjs 6.11.3 treats user-controlled field names as trusted object keys — any field named __proto__ in a parsed message can overwrite Object.prototype for the entire process.
  • Both package.json and package-lock.json must be updated — updating only package.json leaves npm ci deployments still installing the vulnerable 6.11.3.
  • The integrity hash change from sha512-xL96... to sha512-5kQW... is the cryptographic proof that the patched tarball is now being used, not the vulnerable one.
  • Prototype pollution via deserialization is silent — it does not throw errors, making it especially dangerous in production systems where observability depends on normal exception handling.
  • Semver ranges like ^6.11.2 do not automatically protect you — the lock file freezes the resolved version until you explicitly regenerate it with a patched release.

How Orbis AppSec Detected This

  • Source: User-controlled binary or JSON protobuf message payload supplied to protobufjs deserialization logic in any code path importing the protobufjs package declared in package-lock.json.
  • Sink: The protobufjs internal object-building routine that assigns decoded field names as JavaScript object keys without sanitizing prototype-polluting names (__proto__, constructor, prototype), present in node_modules/protobufjs version 6.11.3.
  • Missing control: No validation or rejection of field names that correspond to JavaScript prototype chain properties before dynamic property assignment inside the deserialization library.
  • CWE: CWE-1321 — Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution').
  • Fix: Both package.json and package-lock.json were updated to resolve protobufjs at version 6.11.4, which includes internal key sanitization that blocks prototype-polluting field names during message decoding.

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

CVE-2023-36665 is a sharp reminder that deserialization libraries are a high-value attack surface. protobufjs is trusted to faithfully translate binary protobuf data into JavaScript objects — but in version 6.11.3, that trust extended too far, allowing attacker-controlled field names to silently corrupt the JavaScript runtime's prototype chain. The fix is surgical: upgrade to 6.11.4, update both manifest files, and verify the new integrity hash is in place.

More broadly, every library that maps external data to object keys deserves scrutiny. Prototype pollution is notoriously difficult to detect at runtime because it produces no immediate error — its effects manifest elsewhere in the application, often in security checks or configuration reads that suddenly return attacker-controlled values. Automated scanning of your dependency tree, as demonstrated here with Trivy, is the most reliable way to catch these issues before they reach production.


References

Frequently Asked Questions

What is prototype pollution?

Prototype pollution is a JavaScript vulnerability where an attacker injects properties into `Object.prototype` by supplying crafted keys like `__proto__` or `constructor.prototype`, causing those properties to appear on all objects in the application.

How do you prevent prototype pollution in Node.js?

Validate and sanitize all user-controlled keys before using them to set object properties; use `Object.create(null)` for property bags; freeze `Object.prototype`; and keep deserialization libraries like protobufjs up to date.

What CWE is prototype pollution?

Prototype pollution maps to CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution').

Is input validation enough to prevent prototype pollution?

Input validation helps but is not sufficient on its own — the deserialization library itself must also reject dangerous keys. The root fix in protobufjs 6.11.4 is inside the library's own parsing logic.

Can static analysis detect prototype pollution?

Yes. Tools like Trivy (which flagged this exact CVE), Semgrep, and Snyk can detect known-vulnerable versions of protobufjs and prototype-pollution-prone coding patterns in JavaScript.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1402

Related Articles

critical

How Server-Side Template Injection happens in Node.js EJS and how to fix it

CVE-2022-29078 is a critical server-side template injection (SSTI) vulnerability in EJS versions prior to 3.1.7, where the `outputFunctionName` option is passed directly into generated code without sanitization, allowing attackers to execute arbitrary JavaScript on the server. The fix upgrades the EJS dependency from 2.7.4 to 3.1.7+ (resolved here as 6.0.1), eliminating the unsafe code generation path. Any Node.js application rendering EJS templates with user-influenced options is at risk of ful

high

How Prototype Pollution happens in JavaScript via defu and how to fix it

CVE-2026-35209 is a high-severity prototype pollution vulnerability in the `defu` JavaScript library (versions prior to 6.1.5), where a crafted `__proto__` key in the defaults argument can corrupt the global Object prototype. The fix upgrades `defu` from 6.1.4 to 6.1.5 in `pnpm-lock.yaml` and enforces the version via a workspace override, closing the attack surface in production code that depends on `defu` for deep object merging.

critical

How eval() Code Injection happens in JavaScript and how to fix it

A critical code injection vulnerability was discovered in `js/lib/jsencrypt.js` at line 195, where a direct `eval()` call executed a JavaScript string shim for the `process` object in browser environments. If an attacker could influence the string passed to `eval()`—through a compromised dependency, a man-in-the-middle attack, or supply chain tampering—they could achieve arbitrary JavaScript execution in any user's browser. The fix replaces the `eval()` call with the equivalent inline JavaScript

high

How Unsafe eval() in JavaScript Happens in React Components and How to Fix It

A high-severity code injection vulnerability was discovered in `TurnPlanner.tsx`, where the `parseInputExpr` function used JavaScript's `Function` constructor — effectively `eval()` — to evaluate user-provided mathematical expressions. The regex guard in place only checked for the presence of arithmetic operators, not whether the input was safe to execute, leaving the door open for arbitrary JavaScript injection. A targeted whitelist fix was applied to reject any input containing characters outs

high

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

A high-severity prototype pollution vulnerability (CVE-2020-8203) was identified in the lodash library via the `zipObjectDeep` function, present as a transitive dependency through postcss in the project's `yarn.lock`. The fix upgrades postcss from 8.5.8 to 8.5.12 using a Yarn resolution override, eliminating the vulnerable lodash code path and reducing the attack surface against crafted CSS input. This change protects the application from object prototype manipulation that could lead to informat

critical

How Plaintext Credential Storage Happens in Node.js Config Files and How to Fix It

A critical vulnerability in `config.js` allowed OAuth tokens and user IDs to silently fall back to empty strings when environment variables were unset, enabling credential bypass and potential hardcoded secret exposure. The fix removes the `|| ""` fallback pattern, ensuring credentials are either properly set or explicitly `undefined`, and updates downstream checks to use truthy evaluation instead of empty-string comparison. This change closes a subtle but dangerous gap that could have allowed A