{"id":"CVE-2026-50029","aliases":["EUVD-2026-58791","GHSA-m34p-749j-x6m6"],"status":"published","title":"js-toml has silent type confusion via falsy-primitive duplicate-key bypass","description":"js-toml is a TOML parser for JavaScript, Prior to version 1.1.2, the interpreter checks whether a key already exists in a parser-built container with `if (object[key])` instead of `if (key in object)`. When the prior value is a falsy primitive — `false`, `0`, `0n`, `0.0`, `-0`, or `\"\"` — the duplicate-key branch is skipped and the value is silently overwritten by a later sub-table, dotted-key sub-table, or array-of-tables sharing the same name. Per the TOML 1.0.0 spec (\"Defining a key multiple times is invalid\"; \"You cannot define any key or table more than once\"), this should be a parse error. The result is structural type confusion of attacker-named keys in the value returned by `load()`. A boolean-typed `false` (or numeric `0`) becomes a truthy object. Host applications that gate behavior on `if (config.flag)`, `if (!user.banned)`, `if (config.allowDelete)`, or `if (config.publicMode)` will silently take the truthy branch. This is distinct from GHSA-65fc-cr5f-v7r2 (the 1.0.2 prototype-pollution fix). `Object.prototype` is not polluted. The `Object.create(null)` mitigation from 1.0.2 is intact; the bug here is in the duplicate-key state machine, not in container construction. Version 1.1.2 patches the incorrect comparison.","severity":{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00398,"percentile":0.34499,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-697"],"published":"2026-06-26T22:49:28Z","modified":"2026-10-28T02:30:59Z","fixes":[{"ecosystem":"npm","package":"js-toml","fixed_in":["1.1.2"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"js-toml","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"1.1.1"},{"fixed":"1.1.2"}]}]},{"source":"osv","ecosystem":"npm","package":"js-toml","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.1.2"}]}]}],"products":[{"vendor":"sunnyadn","product":"js-toml"}],"references":[{"url":"https://github.com/sunnyadn/js-toml/security/advisories/GHSA-m34p-749j-x6m6","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/sunnyadn/js-toml/commit/e0504fa5d3dcde2d1d588c9001c24b7b700beeeb","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/sunnyadn/js-toml/releases/tag/v1.1.2","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-m34p-749j-x6m6"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/50xxx/CVE-2026-50029.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-50029","tags":["advisory"]},{"url":"https://github.com/sunnyadn/js-toml","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-50029","title":"js-toml has silent type confusion via falsy-primitive duplicate-key bypass","description":"js-toml is a TOML parser for JavaScript, Prior to version 1.1.2, the interpreter checks whether a key already exists in a parser-built container with `if (object[key])` instead of `if (key in object)`. When the prior value is a falsy primitive — `false`, `0`, `0n`, `0.0`, `-0`, or `\"\"` — the duplicate-key branch is skipped and the value is silently overwritten by a later sub-table, dotted-key sub-table, or array-of-tables sharing the same name. Per the TOML 1.0.0 spec (\"Defining a key multiple times is invalid\"; \"You cannot define any key or table more than once\"), this should be a parse error. The result is structural type confusion of attacker-named keys in the value returned by `load()`. A boolean-typed `false` (or numeric `0`) becomes a truthy object. Host applications that gate behavior on `if (config.flag)`, `if (!user.banned)`, `if (config.allowDelete)`, or `if (config.publicMode)` will silently take the truthy branch. This is distinct from GHSA-65fc-cr5f-v7r2 (the 1.0.2 prototype-pollution fix). `Object.prototype` is not polluted. The `Object.create(null)` mitigation from 1.0.2 is intact; the bug here is in the duplicate-key state machine, not in container construction. Version 1.1.2 patches the incorrect comparison.","status":"published","published":"2026-08-14T19:08:54.941Z","modified":"2026-08-17T19:34:15.876Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-697"],"updated_at":"2026-11-02T14:20:09.098623Z"}],"euvd":[{"id":"EUVD-2026-58791","description":"js-toml is a TOML parser for JavaScript, Prior to version 1.1.2, the interpreter checks whether a key already exists in a parser-built container with `if (object[key])` instead of `if (key in object)`. When the prior value is a falsy primitive — `false`, `0`, `0n`, `0.0`, `-0`, or `\"\"` — the duplicate-key branch is skipped and the value is silently overwritten by a later sub-table, dotted-key sub-table, or array-of-tables sharing the same name. Per the TOML 1.0.0 spec (\"Defining a key multiple times is invalid\"; \"You cannot define any key or table more than once\"), this should be a parse error. The result is structural type confusion of attacker-named keys in the value returned by `load()`. A boolean-typed `false` (or numeric `0`) becomes a truthy object. Host applications that gate behavior on `if (config.flag)`, `if (!user.banned)`, `if (config.allowDelete)`, or `if (config.publicMode)` will silently take the truthy branch. This is distinct from GHSA-65fc-cr5f-v7r2 (the 1.0.2 prototype-pollution fix). `Object.prototype` is not polluted. The `Object.create(null)` mitigation from 1.0.2 is intact; the bug here is in the duplicate-key state machine, not in container construction. Version 1.1.2 patches the incorrect comparison.","status":"published","published":"2026-08-14T19:08:54Z","modified":"2026-08-17T19:34:15Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T18:01:48.395378Z"}],"ghsa":[{"id":"GHSA-m34p-749j-x6m6","title":"js-toml has silent type confusion via falsy-primitive duplicate-key bypass","description":"### Summary\n\n`js-toml`'s interpreter checks whether a key already exists in a parser-built container with `if (object[key])` instead of `if (key in object)`. When the prior value is a falsy primitive — `false`, `0`, `0n`, `0.0`, `-0`, or `\"\"` — the duplicate-key branch is skipped and the value is silently overwritten by a later sub-table, dotted-key sub-table, or array-of-tables sharing the same name. Per the TOML 1.0.0 spec (\"Defining a key multiple times is invalid\"; \"You cannot define any key or table more than once\"), this should be a parse error.\n\nThe result is **structural type confusion of attacker-named keys** in the value returned by `load()`. A boolean-typed `false` (or numeric `0`) becomes a truthy object. Host applications that gate behavior on `if (config.flag)`, `if (!user.banned)`, `if (config.allowDelete)`, or `if (config.publicMode)` will silently take the truthy branch.\n\nThis is **distinct** from [GHSA-65fc-cr5f-v7r2](https://github.com/sunnyadn/js-toml/security/advisories/GHSA-65fc-cr5f-v7r2) (the 1.0.2 prototype-pollution fix). `Object.prototype` is **not** polluted. The `Object.create(null)` mitigation from 1.0.2 is intact; the bug here is in the duplicate-key state machine, not in container construction.\n\n### Details\n\nTwo truthy checks are wrong:\n\n`src/load/interpreter.ts:214` — `Interpreter.tryCreatingObject`\n\n```js\nif (object[key]) {            // falsy primitives slip through\n    // duplicate-key logic\n} else {\n    object[key] = createSafeObject();   // silently overwrites the prior falsy value\n    ...\n}\n```\n\n`src/load/interpreter.ts:278` — `Interpreter.getOrCreateArray`\n\n```js\nif (object[first] \u0026\u0026 !Array.isArray(object[first])) {   // same flaw\n    throw new DuplicateKeyError();\n}\nobject[first] = object[first] || [];   // overwrites the prior falsy value\n```\n\nBoth should use the `in` operator. Containers are created via `Object.create(null)`, so `in` is unambiguous (no inherited keys to worry about).\n\nThe bug is reachable through every parent-walking interpreter path:\n\n- `assignValue` — dotted keys in `key = value`\n- `createTable` — `[stdTable]` headers\n- `getOrCreateArray` — `[[arrayOfTables]]` headers\n\n### PoC\n\n```toml\nisAdmin = false\n[isAdmin]\nforced = \"yes\"\n```\n\n```js\nimport { load } from 'js-toml';\n\nconst config = load(`\nisAdmin = false\n[isAdmin]\nforced = \"yes\"\n`);\n\nconsole.log(JSON.stringify(config));\n// {\"isAdmin\":{\"forced\":\"yes\"}}\n\nconsole.log(config.isAdmin ? 'BYPASS' : 'safe');\n// BYPASS\n\nif (config.isAdmin) {\n  // attacker reaches admin-only code\n}\n```\n\n### Impact\n\nSpec-violating input acceptance leading to structural type confusion. (CWE-697)\n\n### Suggested fix\n\nin `src/load/interpreter.ts`\n\n```diff\nexport class Interpreter extends BaseCstVisitor {\n     ignoreImplicitDeclared,\n     ignoreExplicitDeclared\n   ) {\n-    if (object[key]) {\n+    if (key in object) {\n       if (\n         !isPlainObject(object[key]) ||\n         (!ignoreExplicitDeclared \u0026\u0026\n```\n```diff\nexport class Interpreter extends BaseCstVisitor {\n       return this.getOrCreateArray(keys, object[first], idx + 1);\n     }\n\n-    if (object[first] \u0026\u0026 !Array.isArray(object[first])) {\n+    if (first in object \u0026\u0026 !Array.isArray(object[first])) {\n       throw new DuplicateKeyError();\n     }\n\n     object[first] = object[first] || [];\n```","status":"published","published":"2026-06-26T22:49:28Z","modified":"2026-07-21T17:30:01Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium"}],"cwes":["CWE-697"],"updated_at":"2026-11-02T14:40:50.444681Z"}],"nvd":[{"id":"CVE-2026-50029","description":"js-toml is a TOML parser for JavaScript, Prior to version 1.1.2, the interpreter checks whether a key already exists in a parser-built container with `if (object[key])` instead of `if (key in object)`. When the prior value is a falsy primitive — `false`, `0`, `0n`, `0.0`, `-0`, or `\"\"` — the duplicate-key branch is skipped and the value is silently overwritten by a later sub-table, dotted-key sub-table, or array-of-tables sharing the same name. Per the TOML 1.0.0 spec (\"Defining a key multiple times is invalid\"; \"You cannot define any key or table more than once\"), this should be a parse error. The result is structural type confusion of attacker-named keys in the value returned by `load()`. A boolean-typed `false` (or numeric `0`) becomes a truthy object. Host applications that gate behavior on `if (config.flag)`, `if (!user.banned)`, `if (config.allowDelete)`, or `if (config.publicMode)` will silently take the truthy branch. This is distinct from GHSA-65fc-cr5f-v7r2 (the 1.0.2 prototype-pollution fix). `Object.prototype` is not polluted. The `Object.create(null)` mitigation from 1.0.2 is intact; the bug here is in the duplicate-key state machine, not in container construction. Version 1.1.2 patches the incorrect comparison.","status":"published","published":"2026-08-14T19:17:18.98Z","modified":"2026-09-18T20:09:01.757Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-697"],"updated_at":"2026-11-02T14:12:37.926464Z"}],"osv":[{"id":"CVE-2026-50029","title":"js-toml has silent type confusion via falsy-primitive duplicate-key bypass","description":"js-toml is a TOML parser for JavaScript, Prior to version 1.1.2, the interpreter checks whether a key already exists in a parser-built container with `if (object[key])` instead of `if (key in object)`. When the prior value is a falsy primitive — `false`, `0`, `0n`, `0.0`, `-0`, or `\"\"` — the duplicate-key branch is skipped and the value is silently overwritten by a later sub-table, dotted-key sub-table, or array-of-tables sharing the same name. Per the TOML 1.0.0 spec (\"Defining a key multiple times is invalid\"; \"You cannot define any key or table more than once\"), this should be a parse error. The result is structural type confusion of attacker-named keys in the value returned by `load()`. A boolean-typed `false` (or numeric `0`) becomes a truthy object. Host applications that gate behavior on `if (config.flag)`, `if (!user.banned)`, `if (config.allowDelete)`, or `if (config.publicMode)` will silently take the truthy branch. This is distinct from GHSA-65fc-cr5f-v7r2 (the 1.0.2 prototype-pollution fix). `Object.prototype` is not polluted. The `Object.create(null)` mitigation from 1.0.2 is intact; the bug here is in the duplicate-key state machine, not in container construction. Version 1.1.2 patches the incorrect comparison.","status":"published","published":"2026-08-14T19:08:54.941Z","modified":"2026-10-28T02:30:59.230642757Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium"}],"cwes":["CWE-697"],"updated_at":"2026-11-02T15:45:07.060148Z"},{"id":"GHSA-m34p-749j-x6m6","title":"js-toml has silent type confusion via falsy-primitive duplicate-key bypass","description":"### Summary\n\n`js-toml`'s interpreter checks whether a key already exists in a parser-built container with `if (object[key])` instead of `if (key in object)`. When the prior value is a falsy primitive — `false`, `0`, `0n`, `0.0`, `-0`, or `\"\"` — the duplicate-key branch is skipped and the value is silently overwritten by a later sub-table, dotted-key sub-table, or array-of-tables sharing the same name. Per the TOML 1.0.0 spec (\"Defining a key multiple times is invalid\"; \"You cannot define any key or table more than once\"), this should be a parse error.\n\nThe result is **structural type confusion of attacker-named keys** in the value returned by `load()`. A boolean-typed `false` (or numeric `0`) becomes a truthy object. Host applications that gate behavior on `if (config.flag)`, `if (!user.banned)`, `if (config.allowDelete)`, or `if (config.publicMode)` will silently take the truthy branch.\n\nThis is **distinct** from [GHSA-65fc-cr5f-v7r2](https://github.com/sunnyadn/js-toml/security/advisories/GHSA-65fc-cr5f-v7r2) (the 1.0.2 prototype-pollution fix). `Object.prototype` is **not** polluted. The `Object.create(null)` mitigation from 1.0.2 is intact; the bug here is in the duplicate-key state machine, not in container construction.\n\n### Details\n\nTwo truthy checks are wrong:\n\n`src/load/interpreter.ts:214` — `Interpreter.tryCreatingObject`\n\n```js\nif (object[key]) {            // falsy primitives slip through\n    // duplicate-key logic\n} else {\n    object[key] = createSafeObject();   // silently overwrites the prior falsy value\n    ...\n}\n```\n\n`src/load/interpreter.ts:278` — `Interpreter.getOrCreateArray`\n\n```js\nif (object[first] \u0026\u0026 !Array.isArray(object[first])) {   // same flaw\n    throw new DuplicateKeyError();\n}\nobject[first] = object[first] || [];   // overwrites the prior falsy value\n```\n\nBoth should use the `in` operator. Containers are created via `Object.create(null)`, so `in` is unambiguous (no inherited keys to worry about).\n\nThe bug is reachable through every parent-walking interpreter path:\n\n- `assignValue` — dotted keys in `key = value`\n- `createTable` — `[stdTable]` headers\n- `getOrCreateArray` — `[[arrayOfTables]]` headers\n\n### PoC\n\n```toml\nisAdmin = false\n[isAdmin]\nforced = \"yes\"\n```\n\n```js\nimport { load } from 'js-toml';\n\nconst config = load(`\nisAdmin = false\n[isAdmin]\nforced = \"yes\"\n`);\n\nconsole.log(JSON.stringify(config));\n// {\"isAdmin\":{\"forced\":\"yes\"}}\n\nconsole.log(config.isAdmin ? 'BYPASS' : 'safe');\n// BYPASS\n\nif (config.isAdmin) {\n  // attacker reaches admin-only code\n}\n```\n\n### Impact\n\nSpec-violating input acceptance leading to structural type confusion. (CWE-697)\n\n### Suggested fix\n\nin `src/load/interpreter.ts`\n\n```diff\nexport class Interpreter extends BaseCstVisitor {\n     ignoreImplicitDeclared,\n     ignoreExplicitDeclared\n   ) {\n-    if (object[key]) {\n+    if (key in object) {\n       if (\n         !isPlainObject(object[key]) ||\n         (!ignoreExplicitDeclared \u0026\u0026\n```\n```diff\nexport class Interpreter extends BaseCstVisitor {\n       return this.getOrCreateArray(keys, object[first], idx + 1);\n     }\n\n-    if (object[first] \u0026\u0026 !Array.isArray(object[first])) {\n+    if (first in object \u0026\u0026 !Array.isArray(object[first])) {\n       throw new DuplicateKeyError();\n     }\n\n     object[first] = object[first] || [];\n```","status":"published","published":"2026-06-26T22:49:28Z","modified":"2026-07-21T17:30:26.096074019Z","cvss":[{"version":"3.1","score":5.3,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N","level":"medium"}],"cwes":["CWE-697"],"updated_at":"2026-11-02T15:46:51.10002Z"}]}}
