GHSA-h895-cgqg-jf52: Npm/tome vulnerability

Published Oct 22, 2026
·
Updated

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.

Affected Software

1 affected componentFixes available
npm/tome<=5.0.0
5.0.1

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/tome to a version that resolves this vulnerability.

    Fixed in 5.0.1
  2. Upgrade

    Upgrade npm/toml to a version that resolves this vulnerability.

    Fixed in 4.1.2Patch CVE-2026-63376

Event History

Oct 22, 2026
Advisory Published
via GitHub·04:39 PM
Data Sourced
via GitHub·04:39 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

What input must an attacker control to exploit this issue?

The attacker must be able to supply a crafted TOML document that the application parses with toml.parse(). The described proof of concept uses a four-line TOML document.

2

What is the practical impact after exploitation?

The parser can write a property to Object.prototype. That property is then visible to every object in the process, including objects created by unrelated libraries.

3

Is this limited to TOML keys containing dots?

No. The described bypass uses a quote character in a key path rather than a dot, causing two distinct paths to render to the same encoded path string.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203