Public mode

No access to your tenant or devices. Every item links to its source. What this means

Some sources, documents or advisories need attention: 274/275 feeds/APIs · 469/469 docs · 8/9 advisories · checked 14:12 UTC Customize Public modeDiscuss ChangeIntel on Discord

Tinypool: Prototype Pollution Gadget to RCE in run() options

Exact source revision recorded 24 Oct 2026 13:17:17 UTC. It does not establish the source's current position. Latest evidence.

osv · GHSA-85c8-ppgw-ccpr

Published

`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`. An 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. This is the tinypool counterpart to the piscina root discovery [GHSA-x9g3-xrwr-cwfg](https://github.com/piscinajs/piscina/security/advisories/GHSA-x9g3-xrwr-cwfg). `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()`. ## Impact Arbitrary 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. ## Proof of Concept ```js // legitimate-worker.mjs export default async (task) => ({ by: 'legitimate-worker', processed: task }) // malicious-worker.mjs export default async (task) => ({ by: 'attacker', stolenRequestBody: task }) // main.js import express from "express"; import Tinypool from "tinypool"; import { fileURLToPath } from "node:url"; import path from "node:path"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Simulate upstream prototype pollution (lodash merge, qs parse, etc.) Object.prototype.filename = path.join(__dirname, "malicious-worker.mjs"); const pool = new Tinypool({ filename: path.join(__dirname, "legitimate-worker.mjs"), }); express() .use(express.json()) .post("/", async (req, res) => { const ac = new AbortController(); const result = await pool.run(req.body, { signal: ac.signal }); res.json(result); }) .listen(31337); ``` ## Suggested fix Read 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.

Source published
5 Oct 2026 22:49:52 UTC
Source modified
5 Oct 2026 23:00:05 UTC
Observed here
24 Oct 2026 13:17:17 UTC
Source terms
GitHub Advisory Database · CC-BY-4.0

Original source record · Cite this exact observation · Complete original publisher JSON

This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.

CVSS_V4 · 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

Publisher rating · CRITICAL

ChangeIntel

An IT change radar: releases, security, known issues, retirements, documentation changes, and service status from public sources. Every item links to supporting evidence; dates and statuses can change after they are read.

Sources read 11 Nov 14:12 UTC · 274 of 275 readable · 273 fully read · documentation 469/469 current