high · 8.2CVE-2026-63376Sep 3, 2026

CVE-2026-63376: toml Prototype Pollution via __proto__ Key-Path Desynchronization

Shubham Kandhare
Security Engagement Manager, SecureLayer7

The toml npm package lets an attacker corrupt Object.prototype for the entire Node.js process by feeding toml.parse() a crafted TOML document that routes a table path through a scalar value and into…

Packagetoml
Ecosystemnpm
Affected< 4.1.2
Fixed in4.1.2
CVE-2026-63376: toml Prototype Pollution via __proto__ Key-Path Desynchronization

The problem

toml.parse() in lib/compiler.js resolves table paths by walking the live object graph in deepRef(). When a path segment is __proto__, the walker follows it right through a scalar value (e.g. a number) into Number.prototype and then Object.prototype.

The duplicate-key guard that should block this traversal is defeated by a type mismatch: setPath stores currentPath as an array while addTableArray stores it as a string. When the guard later constructs a dot-joined lookup key it coerces the array via Array.toString(), producing comma-separated segments ("a,b.y") instead of dot-separated ones ("a.b.y").

The Set lookup misses, the guard never fires, and traversal continues into Object.prototype.

Proof of concept

A working proof-of-concept for CVE-2026-63376 in toml, with the exact payload below.

javascript
const toml = require("toml"); // npm install toml@4.1.1
delete Object.prototype.polluted;

toml.parse(`
[a.b]
y = 1
[a.b.y.__proto__.__proto__]
polluted = "yes"
`);

console.log(({}).polluted); // -> "yes"  (Object.prototype is now corrupted)

// Alternate route via table-array prefix clearing:
toml.parse(`
aa = 1
[[a]]
[aa.__proto__.__proto__]
polluted = "yes"
`);
console.log(({}).polluted); // -> "yes"

Two root causes combine (CWE-1321). First, deepRef() calls ctx = ctx[key] for every path segment with no reserved-key check, so __proto__ is treated as a normal traversable property. Tables the compiler creates use Object.create(null), but scalar values stored in those tables carry normal prototypes, giving the attacker a path into Object.prototype through any scalar.

Second, the guard that should detect reuse of an already-assigned scalar path is stored in a Set under comma-joined keys (because currentPath is an array that gets coerced by Array.toString()) while deepRef builds dot-joined lookup strings. The Set never matches, so the guard never raises an error.

The fix in commits def6ab5 and dfaff662 adds an explicit denylist check for __proto__, constructor, and prototype at each step of deepRef, and normalises currentPath to always be a string so the tracking keys and the traversal keys use the same format.

The fix

Upgrade to toml 4.1.2 or later. No configuration workaround exists for earlier versions; the only safe option is to avoid passing untrusted TOML input to toml.parse() until the upgrade is applied.

Reported by Duy Bui.

References: [1][2][3][4]

Related research