| # TensorFlow.js (tfjs-node) — arbitrary file read via weightsManifest path traversal (CWE-22) | |
| Loading an untrusted TensorFlow.js model with `@tensorflow/tfjs-node` reads arbitrary host files. | |
| A model is a `model.json` plus weight files named in `weightsManifest[].paths`. The Node filesystem | |
| loader resolves each weight path with `join(modelDir, path)` and `readFile`s it, with **no containment | |
| check** — so weight paths containing `../` escape the model directory. | |
| ## Reproduce | |
| 1. `npm install @tensorflow/tfjs-node` | |
| 2. `unzip poc.zip` (gives `model.json` with weightsManifest path `../../../../../../../../etc/hostname`, and `poc.js`) | |
| 3. `node poc.js model.json` | |
| 4. Output: the loaded model's weight buffer contains the bytes of `/etc/hostname` (a file outside the model dir). | |
| Affected API: `tf.loadLayersModel('file://.../model.json')`, `tf.loadGraphModel(...)`, `tf.io.fileSystem(...).load()`. | |
| ## Root cause | |
| `tfjs-node/src/io/file_system.ts` `loadWeights()`: | |
| `const weightFilePath = join(dirName, path); const buffer = await readFile(weightFilePath);` | |
| No `..`/absolute rejection, no normalize, no `startsWith(dir)` containment. | |
| ## Fix | |
| Resolve and verify each weight path stays within the model directory; reject `..`/absolute paths. | |
| ## Not a duplicate | |
| No CVE/GHSA/OSV for `@tensorflow/tfjs-node`; no huntr report; web search found no matching advisory. | |