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.


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.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1402

Related Articles

high

How Regular Expression Denial of Service (ReDoS) Happens in Node.js trim-newlines and How to Fix It

CVE-2021-33623 exposed a Regular Expression Denial of Service (ReDoS) vulnerability in the npm package `trim-newlines` versions 1.0.0 and earlier. The vulnerable `.end()` method used an inefficient regex pattern that could cause severe performance degradation when processing malicious input. Upgrading to version 4.0.1 patches the regex implementation and eliminates the attack surface.

critical

How CSS Injection via Weak Pattern Validation happens in Vue.js and how to fix it

A critical CSS injection vulnerability in `testpage/App.vue` allowed attackers to bypass weak HTML5 pattern validation and load malicious stylesheets. The fix replaces direct variable assignment with a hardened `setCustomStylesheetHref()` method using strict regex validation.

critical

How Unvalidated Dynamic Component Loading happens in TypeScript/Viewi and how to fix it

A critical vulnerability in Viewi's component loader allowed attackers to inject malicious JavaScript through compromised or MITM-attacked external component servers. The fix adds proper HTTP response validation before parsing dynamically fetched JSON components.

high

How Denial of Service via Crafted ZIP File happens in Node.js and how to fix it

CVE-2026-39244 is a high-severity denial of service vulnerability in the adm-zip npm package that allows attackers to crash Node.js applications by uploading maliciously crafted ZIP files. The fix upgrades adm-zip from version 0.5.16 to 0.6.0, which adds proper memory bounds checking to prevent excessive allocation during archive extraction.

critical

How prototype pollution happens in JavaScript AST traversal and how to fix it

A critical prototype pollution primitive was fixed in `src/traverse/estraverse` where visitor-supplied child keys were merged with `Object.assign(Object.create(this.__keys), visitor.keys)`. Because `Object.assign` uses assignment semantics, a key literally named `__proto__` reached the `Object.prototype` setter and rewired the prototype chain of the traversal key map instead of being stored as data. The fix replaces the merge with an object spread (`{ ...VisitorKeys, ...visitor.keys }`), which *

critical

How SQL injection happens in Python DuckDB view creation and how to fix it

A critical SQL injection flaw in `python/src/idx/api.py:265` built five DuckDB `CREATE VIEW` statements with Python f-strings, interpolating a filesystem path directly into SQL text. The fix replaces the interpolated path with a bound parameter (`read_parquet(?)`) and moves the view names into a hardcoded, non-interpolated statement map — eliminating any path where filenames or directory values can alter SQL structure.