{"id":"CVE-2026-104849","aliases":["CLSA-2026-1792490437","CLSA-2026-1792490541","EUVD-2026-91719","GHSA-85c8-ppgw-ccpr","ROOT-APP-NPM-CVE-2026-104849"],"status":"published","title":"Tinypool: Prototype Pollution Gadget to RCE in run() options","description":"Tinypool is a minimal Node.js worker thread pool implementation. Prior to 2.1.2, Tinypool reads filename from a caller-supplied options object in pool.run(task, options) without requiring an own property, so a polluted Object.prototype.filename can replace the intended worker module. Applications are affected only when they pass their own second-argument options object to pool.run(); calls without that argument use the trusted default options object. An attacker who can first pollute the prototype can cause the worker pool to load attacker-selected JavaScript and can read or modify task data with the host process's privileges. This issue is fixed in version 2.1.2.","severity":{"version":"4.0","score":9.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H","level":"critical","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00485,"percentile":0.42277,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":true,"fix_available":true},"cwes":["CWE-1321","CWE-94"],"published":"2026-10-02T16:17:28Z","modified":"2026-10-28T02:30:37Z","exploits":[{"source":"pocgithub","id":"CompleteTech-Security/tinypool-options-rce-check","title":"CompleteTech-Security/tinypool-options-rce-check","date":"2026-10-23","url":"https://github.com/CompleteTech-Security/tinypool-options-rce-check"}],"fixes":[{"ecosystem":"npm","package":"tinypool","fixed_in":["2.1.2"],"sources":["ghsa","osv"]},{"ecosystem":"Root:npm","package":"tinypool","fixed_in":["1.1.1-aikido.3","0.8.4-aikido.3"],"sources":["osv"]},{"ecosystem":"Root:npm","package":"@rootio/tinypool","fixed_in":["1.1.1-root.io.3","0.8.4-root.io.3"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"tinypool","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"2.1.2"}]}]},{"source":"osv","ecosystem":"TuxCare:npm","package":"tinypool","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.1.1-tuxcare.0"},{"fixed":"1.1.1-tuxcare.1"}]}],"versions":["1.1.1"],"bundled":2},{"source":"osv","ecosystem":"TuxCare:npm","package":"tinypool","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"2.1.0-tuxcare.0"},{"fixed":"2.1.0-tuxcare.1"}]}],"versions":["2.1.0"],"bundled":2},{"source":"osv","ecosystem":"npm","package":"tinypool","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"2.1.2"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"tinypool","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.1.1-aikido.3"},{"fixed":"0.8.4-aikido.3"}]}]},{"source":"osv","ecosystem":"Root:npm","package":"@rootio/tinypool","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.1.1-root.io.3"},{"fixed":"0.8.4-root.io.3"}]}]}],"products":[{"vendor":"tinylibs","product":"tinypool"}],"references":[{"url":"https://github.com/tinylibs/tinypool/security/advisories/GHSA-85c8-ppgw-ccpr","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/tinylibs/tinypool/pull/135","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/tinylibs/tinypool/commit/f41411a3e23324c674f35a19a3240f7a7c40ffbf","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/tinylibs/tinypool/releases/tag/v2.1.2","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-104849","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-85c8-ppgw-ccpr"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/104xxx/CVE-2026-104849.json","tags":["advisory"]},{"url":"https://github.com/tinylibs/tinypool","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-104849","title":"Tinypool: Prototype Pollution Gadget to RCE in run() options","description":"Tinypool is a minimal Node.js worker thread pool implementation. Prior to 2.1.2, Tinypool reads filename from a caller-supplied options object in pool.run(task, options) without requiring an own property, so a polluted Object.prototype.filename can replace the intended worker module. Applications are affected only when they pass their own second-argument options object to pool.run(); calls without that argument use the trusted default options object. An attacker who can first pollute the prototype can cause the worker pool to load attacker-selected JavaScript and can read or modify task data with the host process's privileges. This issue is fixed in version 2.1.2.","status":"published","published":"2026-10-02T16:17:28.062Z","modified":"2026-10-06T16:35:59.948Z","cvss":[{"version":"4.0","score":9.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H","level":"critical","assessor":"cna"}],"cwes":["CWE-94","CWE-1321"],"updated_at":"2026-11-02T14:20:09.448255Z"}],"euvd":[{"id":"EUVD-2026-91719","description":"Tinypool is a minimal Node.js worker thread pool implementation. Prior to 2.1.2, Tinypool reads filename from a caller-supplied options object in pool.run(task, options) without requiring an own property, so a polluted Object.prototype.filename can replace the intended worker module. Applications are affected only when they pass their own second-argument options object to pool.run(); calls without that argument use the trusted default options object. An attacker who can first pollute the prototype can cause the worker pool to load attacker-selected JavaScript and can read or modify task data with the host process's privileges. This issue is fixed in version 2.1.2.","status":"published","published":"2026-10-02T16:17:28Z","modified":"2026-10-06T16:35:59Z","cvss":[{"version":"4.0","score":9.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H","level":"critical"}],"updated_at":"2026-11-02T17:27:37.480805Z"}],"ghsa":[{"id":"GHSA-85c8-ppgw-ccpr","title":"Tinypool: Prototype Pollution Gadget to RCE in run() options","description":"`tinypool` is a fork of `piscina` and inherited the same prototype-pollution surface. When `pool.run(task, options)` is called, the `filename` option is read from the provided `options` object. If that object does not have an own `filename` property, the lookup falls through to `Object.prototype`.\n\nAn attacker who can pollute `Object.prototype.filename` (for example, via a vulnerable `lodash.merge`, `qs.parse`, or similar elsewhere in the application) can make tinypool load and execute an attacker-controlled worker module.\n\nThis is the tinypool counterpart to the piscina root discovery [GHSA-x9g3-xrwr-cwfg](https://github.com/piscinajs/piscina/security/advisories/GHSA-x9g3-xrwr-cwfg).\n\n`pool.run(task)` with no second argument is not affected, because `kDefaultOptions.filename` is `null` and the options object is not user-controlled. The exploit only triggers when the caller passes their own options object to `pool.run()`.\n\n## Impact\n\nArbitrary JavaScript execution in the worker pool. If the application passes attacker-controlled data as the `run()` task and also supplies a `run()` options object, the attacker can redirect execution to a malicious worker that exfiltrates or modifies that data, achieving remote code execution and/or data exfiltration.\n\n## Proof of Concept\n\n```js\n// legitimate-worker.mjs\nexport default async (task) =\u003e ({ by: 'legitimate-worker', processed: task })\n\n// malicious-worker.mjs\nexport default async (task) =\u003e ({ by: 'attacker', stolenRequestBody: task })\n\n// main.js\nimport express from \"express\";\nimport Tinypool from \"tinypool\";\nimport { fileURLToPath } from \"node:url\";\nimport path from \"node:path\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// Simulate upstream prototype pollution (lodash merge, qs parse, etc.)\nObject.prototype.filename = path.join(__dirname, \"malicious-worker.mjs\");\n\nconst pool = new Tinypool({\n  filename: path.join(__dirname, \"legitimate-worker.mjs\"),\n});\n\nexpress()\n  .use(express.json())\n  .post(\"/\", async (req, res) =\u003e {\n    const ac = new AbortController();\n    const result = await pool.run(req.body, { signal: ac.signal });\n    res.json(result);\n  })\n  .listen(31337);\n```\n\n## Suggested fix\n\nRead all user-supplied options from own properties only (`Object.hasOwn` or `Object.prototype.hasOwnProperty.call`) and build the internal `ThreadPool.options` object with a null prototype.","status":"published","published":"2026-10-05T22:49:52Z","modified":"2026-10-05T22:49:53Z","cvss":[{"version":"4.0","score":9.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H","level":"critical"}],"cwes":["CWE-94","CWE-1321"],"updated_at":"2026-11-02T14:41:25.889593Z"}],"nvd":[{"id":"CVE-2026-104849","description":"Tinypool is a minimal Node.js worker thread pool implementation. Prior to 2.1.2, Tinypool reads filename from a caller-supplied options object in pool.run(task, options) without requiring an own property, so a polluted Object.prototype.filename can replace the intended worker module. Applications are affected only when they pass their own second-argument options object to pool.run(); calls without that argument use the trusted default options object. An attacker who can first pollute the prototype can cause the worker pool to load attacker-selected JavaScript and can read or modify task data with the host process's privileges. This issue is fixed in version 2.1.2.","status":"published","published":"2026-10-02T17:17:03.623Z","modified":"2026-10-06T17:17:15.7Z","cvss":[{"version":"4.0","score":9.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/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","level":"critical","assessor":"secondary"}],"cwes":["CWE-94","CWE-1321"],"updated_at":"2026-11-02T14:12:53.75133Z"}],"osv":[{"id":"CVE-2026-104849","title":"Tinypool: Prototype Pollution Gadget to RCE in run() options","description":"Tinypool is a minimal Node.js worker thread pool implementation. Prior to 2.1.2, Tinypool reads filename from a caller-supplied options object in pool.run(task, options) without requiring an own property, so a polluted Object.prototype.filename can replace the intended worker module. Applications are affected only when they pass their own second-argument options object to pool.run(); calls without that argument use the trusted default options object. An attacker who can first pollute the prototype can cause the worker pool to load attacker-selected JavaScript and can read or modify task data with the host process's privileges. This issue is fixed in version 2.1.2.","status":"published","published":"2026-10-02T16:17:28.062Z","modified":"2026-10-28T02:30:37.506447914Z","cvss":[{"version":"4.0","score":9.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H","level":"critical"}],"cwes":["CWE-1321","CWE-94"],"updated_at":"2026-11-02T15:44:54.236592Z"},{"id":"GHSA-85c8-ppgw-ccpr","title":"Tinypool: Prototype Pollution Gadget to RCE in run() options","description":"`tinypool` is a fork of `piscina` and inherited the same prototype-pollution surface. When `pool.run(task, options)` is called, the `filename` option is read from the provided `options` object. If that object does not have an own `filename` property, the lookup falls through to `Object.prototype`.\n\nAn attacker who can pollute `Object.prototype.filename` (for example, via a vulnerable `lodash.merge`, `qs.parse`, or similar elsewhere in the application) can make tinypool load and execute an attacker-controlled worker module.\n\nThis is the tinypool counterpart to the piscina root discovery [GHSA-x9g3-xrwr-cwfg](https://github.com/piscinajs/piscina/security/advisories/GHSA-x9g3-xrwr-cwfg).\n\n`pool.run(task)` with no second argument is not affected, because `kDefaultOptions.filename` is `null` and the options object is not user-controlled. The exploit only triggers when the caller passes their own options object to `pool.run()`.\n\n## Impact\n\nArbitrary JavaScript execution in the worker pool. If the application passes attacker-controlled data as the `run()` task and also supplies a `run()` options object, the attacker can redirect execution to a malicious worker that exfiltrates or modifies that data, achieving remote code execution and/or data exfiltration.\n\n## Proof of Concept\n\n```js\n// legitimate-worker.mjs\nexport default async (task) =\u003e ({ by: 'legitimate-worker', processed: task })\n\n// malicious-worker.mjs\nexport default async (task) =\u003e ({ by: 'attacker', stolenRequestBody: task })\n\n// main.js\nimport express from \"express\";\nimport Tinypool from \"tinypool\";\nimport { fileURLToPath } from \"node:url\";\nimport path from \"node:path\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// Simulate upstream prototype pollution (lodash merge, qs parse, etc.)\nObject.prototype.filename = path.join(__dirname, \"malicious-worker.mjs\");\n\nconst pool = new Tinypool({\n  filename: path.join(__dirname, \"legitimate-worker.mjs\"),\n});\n\nexpress()\n  .use(express.json())\n  .post(\"/\", async (req, res) =\u003e {\n    const ac = new AbortController();\n    const result = await pool.run(req.body, { signal: ac.signal });\n    res.json(result);\n  })\n  .listen(31337);\n```\n\n## Suggested fix\n\nRead all user-supplied options from own properties only (`Object.hasOwn` or `Object.prototype.hasOwnProperty.call`) and build the internal `ThreadPool.options` object with a null prototype.","status":"published","published":"2026-10-05T22:49:52Z","modified":"2026-10-05T23:00:05.32454578Z","cvss":[{"version":"4.0","score":9.5,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H","level":"critical"}],"cwes":["CWE-1321","CWE-94"],"updated_at":"2026-11-02T15:46:41.732368Z"},{"id":"CLSA-2026-1792490437","title":"TuxCare security update for tinypool (2 CVEs)","description":"TuxCare rebuilt tinypool to address 2 CVEs. Fixed in 1.1.1-tuxcare.1.","status":"published","published":"2026-10-20T10:00:37Z","modified":"2026-10-20T13:45:13.44859041Z","updated_at":"2026-11-02T15:43:54.378946Z"},{"id":"CLSA-2026-1792490541","title":"TuxCare security update for tinypool (2 CVEs)","description":"TuxCare rebuilt tinypool to address 2 CVEs. Fixed in 2.1.0-tuxcare.1.","status":"published","published":"2026-10-20T10:02:21Z","modified":"2026-10-20T13:45:13.464637997Z","updated_at":"2026-11-02T15:43:54.378946Z"},{"id":"ROOT-APP-NPM-CVE-2026-104849","title":"CVE-2026-104849 in tinypool - Patched by Root","description":"Root has patched CVE-2026-104849 in the tinypool package for Root:npm. Multiple fixed versions available.","status":"published","published":"2026-10-19T04:30:09Z","modified":"2026-10-19T05:15:04.625471088Z","updated_at":"2026-11-02T15:53:27.362922Z"}]}}
