GHSA-67C8-PQHQ-4RMX
Vulnerability from github – Published: 2026-10-01 15:03 – Updated: 2026-10-01 15:03Summary
A prototype-pollution gadget in ThreadPool.options allows an attacker who can pollute Object.prototype to execute arbitrary code in Piscina worker threads, invoke arbitrary functions during task scheduling, or inject environment variables into workers. The root cause is that ThreadPool.options is created as a plain object inheriting from Object.prototype, so any option without an explicit default in kDefaultOptions can be supplied via the prototype chain.
Details
In src/index.ts the ThreadPool constructor builds the resolved options object as a plain object:
this.options = { ...kDefaultOptions, ...options, filename, maxQueue: 0 }
Because this object has Object.prototype as its prototype, reads for properties that are not own properties of the object and are not present in kDefaultOptions fall back to Object.prototype. This means a prototype-pollution primitive (e.g. from a vulnerable merge() or JSON.parse merge elsewhere in the application) can inject values for execArgv, env, loadBalancer, argv, workerData, resourceLimits, niceIncrement, closeTimeout, recordTiming, stricterFIFO, workerHistogram, and trackUnmanagedFds.
The most serious gadget is execArgv, which is passed directly to new Worker(..., { execArgv }). An attacker can set Object.prototype.execArgv = ['--require', '/tmp/attacker.js'], causing every worker to preload and execute the attacker-controlled module on startup.
This issue survived the fix for GHSA-x9g3-xrwr-cwfg / CVE-2026-55388 ("Prototype Pollution Gadget → RCE via inherited options.filename"). That advisory hardened the Piscina constructor's filename read and run()'s filename/name reads, but ThreadPool.options itself was not created with a null prototype. The same class of attack is therefore still possible against any option without an explicit default in kDefaultOptions.
PoC
import { resolve } from 'node:path'
import Piscina from 'piscina'
Object.prototype.execArgv = ['--require', '/tmp/attacker.js']
const pool = new Piscina({
filename: resolve(import.meta.dirname, 'worker.js'),
minThreads: 1,
maxThreads: 1,
})
await pool.run(1)
/tmp/attacker.js is executed in the worker on startup. A full reproduction repository with execArgv, loadBalancer, and env vectors is available at https://github.com/Fcmam5/piscina-pp-poc.
Impact
- Remote Code Execution: via
execArgv(arbitrary--requiremodule preloaded in every worker on spawn). - Arbitrary code execution in the main thread: via
loadBalancer, an attacker-supplied function that is called during task scheduling. - Environment/CLI option injection: via
env, which is passed to each worker constructor. - Denial of Service / unexpected behavior: via other reachable options such as
workerData,resourceLimits,niceIncrement,closeTimeout,recordTiming, etc.
Anyone using Piscina in an application where Object.prototype can be polluted (e.g. through a dependency with a prototype-pollution vulnerability) is impacted.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "piscina"
},
"ranges": [
{
"events": [
{
"introduced": "5.0.0"
},
{
"fixed": "5.3.2"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "piscina"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.9.4"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "piscina"
},
"ranges": [
{
"events": [
{
"introduced": "6.0.0-rc.1"
},
{
"fixed": "6.0.0-rc.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-102992"
],
"database_specific": {
"cwe_ids": [
"CWE-1321"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-01T15:03:33Z",
"nvd_published_at": "2026-09-30T20:17:27Z",
"severity": "CRITICAL"
},
"details": "### Summary\n\nA prototype-pollution gadget in `ThreadPool.options` allows an attacker who can pollute `Object.prototype` to execute arbitrary code in Piscina worker threads, invoke arbitrary functions during task scheduling, or inject environment variables into workers. The root cause is that `ThreadPool.options` is created as a plain object inheriting from `Object.prototype`, so any option without an explicit default in `kDefaultOptions` can be supplied via the prototype chain.\n\n### Details\n\nIn `src/index.ts` the `ThreadPool` constructor builds the resolved options object as a plain object:\n\n```ts\nthis.options = { ...kDefaultOptions, ...options, filename, maxQueue: 0 }\n```\n\nBecause this object has `Object.prototype` as its prototype, reads for properties that are not own properties of the object and are not present in `kDefaultOptions` fall back to `Object.prototype`. This means a prototype-pollution primitive (e.g. from a vulnerable `merge()` or `JSON.parse` merge elsewhere in the application) can inject values for `execArgv`, `env`, `loadBalancer`, `argv`, `workerData`, `resourceLimits`, `niceIncrement`, `closeTimeout`, `recordTiming`, `stricterFIFO`, `workerHistogram`, and `trackUnmanagedFds`.\n\nThe most serious gadget is `execArgv`, which is passed directly to `new Worker(..., { execArgv })`. An attacker can set `Object.prototype.execArgv = [\u0027--require\u0027, \u0027/tmp/attacker.js\u0027]`, causing every worker to preload and execute the attacker-controlled module on startup.\n\nThis issue survived the fix for **GHSA-x9g3-xrwr-cwfg / CVE-2026-55388** (\"Prototype Pollution Gadget \u2192 RCE via inherited options.filename\"). That advisory hardened the `Piscina` constructor\u0027s `filename` read and `run()`\u0027s `filename`/`name` reads, but `ThreadPool.options` itself was not created with a null prototype. The same class of attack is therefore still possible against any option without an explicit default in `kDefaultOptions`.\n\n### PoC\n\n```js\nimport { resolve } from \u0027node:path\u0027\nimport Piscina from \u0027piscina\u0027\n\nObject.prototype.execArgv = [\u0027--require\u0027, \u0027/tmp/attacker.js\u0027]\n\nconst pool = new Piscina({\n filename: resolve(import.meta.dirname, \u0027worker.js\u0027),\n minThreads: 1,\n maxThreads: 1,\n})\n\nawait pool.run(1)\n```\n\n`/tmp/attacker.js` is executed in the worker on startup. A full reproduction repository with `execArgv`, `loadBalancer`, and `env` vectors is available at https://github.com/Fcmam5/piscina-pp-poc.\n\n### Impact\n\n- **Remote Code Execution**: via `execArgv` (arbitrary `--require` module preloaded in every worker on spawn).\n- **Arbitrary code execution in the main thread**: via `loadBalancer`, an attacker-supplied function that is called during task scheduling.\n- **Environment/CLI option injection**: via `env`, which is passed to each worker constructor.\n- **Denial of Service / unexpected behavior**: via other reachable options such as `workerData`, `resourceLimits`, `niceIncrement`, `closeTimeout`, `recordTiming`, etc.\n\nAnyone using Piscina in an application where `Object.prototype` can be polluted (e.g. through a dependency with a prototype-pollution vulnerability) is impacted.",
"id": "GHSA-67c8-pqhq-4rmx",
"modified": "2026-10-01T15:03:33Z",
"published": "2026-10-01T15:03:33Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/piscinajs/piscina/security/advisories/GHSA-67c8-pqhq-4rmx"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-102992"
},
{
"type": "WEB",
"url": "https://github.com/piscinajs/piscina/commit/0cb12fca37f526065b072592afe954574dcc656f"
},
{
"type": "WEB",
"url": "https://github.com/piscinajs/piscina/commit/2f69f67159a0e48b38fd61fa4a91c2fdc19fff72"
},
{
"type": "WEB",
"url": "https://github.com/piscinajs/piscina/commit/5be7bbb19e3787bb698862cd516121a578d690f7"
},
{
"type": "WEB",
"url": "https://github.com/piscinajs/piscina/commit/bebbda255c2981cecddd36b171b94be2fd41c9a6"
},
{
"type": "PACKAGE",
"url": "https://github.com/piscinajs/piscina"
},
{
"type": "WEB",
"url": "https://github.com/piscinajs/piscina/releases/tag/v4.9.4"
},
{
"type": "WEB",
"url": "https://github.com/piscinajs/piscina/releases/tag/v5.3.2"
},
{
"type": "WEB",
"url": "https://github.com/piscinajs/piscina/releases/tag/v6.0.0-rc.5"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "piscina: Prototype-pollution gadget in ThreadPool.options allows RCE via execArgv / loadBalancer / env"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.