Prototype Pollution via .proto: When Your Schema Poisons the Runtime

protocol-buffers-schema is an npm package for parsing Protocol Buffer schema definitions. It is the schema parser behind protocol-buffers, one of the original Node.js protobuf implementations, maintained by mafintosh. The package has over 100k weekly downloads and 67 direct dependents on npm.

Vulnerability Details

CWE-1321: Improperly Controlled Modification of Object Prototype Attributes (Prototype Pollution)

The parser’s handling of field options allows an attacker to pollute Object.prototype by using __proto__ as a path component inside a .proto schema definition.

When parsing field options like [(foo).bar = "value"], the parser uses reduce to traverse the path and assign the value to a nested object structure. If an attacker provides __proto__ as a path component, the traversal reaches Object.prototype instead of a local object. This allows arbitrary property injection into every object in the Node.js process.

The vulnerable flow:

  1. Parser encounters a field option with a dotted path: [(__proto__).polluted = "HACKED"]
  2. The path is split into segments: ["__proto__", "polluted"]
  3. reduce traverses the segments, walking into obj["__proto__"] — which resolves to Object.prototype
  4. The value "HACKED" is assigned to Object.prototype.polluted
  5. Every object created afterward inherits the injected property

No sanitization, no blocklist, no check for dangerous keys. The path is trusted as-is.

Proof of Concept

const schema = require('protocol-buffers-schema');

console.log('=== BEFORE PARSE ===');
console.log('({}).polluted =', ({}).polluted); // undefined

const malicious = `
syntax = "proto3";
message Exploit {
  string a = 1 [(__proto__).polluted = "HACKED"];
}
`;

schema.parse(malicious);

console.log('\n=== AFTER PARSE ===');
console.log('({}).polluted =', ({}).polluted); // "HACKED"

After parse() executes, every plain object in the process carries the attacker’s property. No special invocation, no post-processing — the pollution happens as a side effect of parsing.

Impact

Prototype pollution is a foundational corruption primitive. Once Object.prototype is tainted, the blast radius depends entirely on what the application does next. Common consequences include:

  • Authentication bypass — if authorization logic checks obj.isAdmin and the polluted prototype supplies a truthy default, access control breaks silently
  • Denial of service — polluting keys that framework internals depend on (e.g., toString, constructor, hasOwnProperty) can crash the process or cause infinite loops
  • Remote code execution — in combination with templating engines, child process spawning, or other gadget chains, prototype pollution has been repeatedly escalated to full RCE in real-world Node.js applications
  • Logic corruption — any code path that reads a property from a plain object without hasOwnProperty checks will pick up attacker-controlled values

The attack surface is particularly concerning because .proto files are often treated as trusted configuration. Teams parse schemas from external sources, shared repositories, and third-party APIs without considering them as executable input. But with this vulnerability, a .proto file becomes a payload.

Why It Was There

The root cause is a pattern that appears across dozens of JavaScript libraries: using bracket notation to walk an attacker-controlled path without guarding against prototype keys.

// Simplified vulnerable pattern
path.reduce((obj, key) => obj[key] = obj[key] || {}, target)

When key is __proto__, obj[key] does not create a new property — it returns the object’s prototype. The traversal silently escapes the local object and starts writing to the shared prototype chain.

This is the same class of bug that has hit lodash, minimist, qs, defu, flatted, and countless other packages. The fix is always the same: reject or skip __proto__, constructor, and prototype during path traversal.

Practical Guidance

If you use protocol-buffers-schema in your project:

  • Check whether you parse .proto files from untrusted or semi-trusted sources (user uploads, external repos, third-party APIs)
  • Audit downstream code for prototype pollution gadgets — template engines, ORM config, and HTTP frameworks are common escalation points
  • Consider freezing Object.prototype in security-sensitive contexts as a defense-in-depth measure:
    Object.freeze(Object.prototype);
    

If you maintain a parser or config loader:

  • Never use reduce or bracket notation to walk user-controlled paths without filtering dangerous keys
  • Block __proto__, constructor, and prototype explicitly in any path traversal logic
  • Add negative test cases that verify prototype pollution does not occur

Disclosure Timeline

Date Event
2026-03-10 Vulnerability disclosed via GitHub PR
2026-04-07 CERT assigns CVE-2026-5758
2026-04-07 PR merged
2026-04-07 Version 3.6.1 released with fix

Advisory

  • CVE-ID: CVE-2026-5758
  • CWE: CWE-1321 — Improperly Controlled Modification of Object Prototype Attributes
  • Affected Package: protocol-buffers-schema < 3.6.1
  • Solution: Update to 3.6.1 or later
  • Credit: Moriel Harush

References