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:
- Parser encounters a field option with a dotted path:
[(__proto__).polluted = "HACKED"] - The path is split into segments:
["__proto__", "polluted"] reducetraverses the segments, walking intoobj["__proto__"]— which resolves toObject.prototype- The value
"HACKED"is assigned toObject.prototype.polluted - 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.isAdminand 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
hasOwnPropertychecks 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
.protofiles 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.prototypein security-sensitive contexts as a defense-in-depth measure:Object.freeze(Object.prototype);
If you maintain a parser or config loader:
- Never use
reduceor bracket notation to walk user-controlled paths without filtering dangerous keys - Block
__proto__,constructor, andprototypeexplicitly 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