--- tags: - security - tensorflow - tfjs - model-security license: mit --- # TensorFlow.js weightsManifest duplicate weight names — silent last-write-wins ## Overview This repository demonstrates a model weight integrity issue in TensorFlow.js layers-model format: a `model.json` can contain the same logical weight name in multiple `weightsManifest` groups with different binary values. `@tensorflow/tfjs` v4.22.0 `loadLayersModel` accepts this artifact, loads all groups sequentially, and silently resolves the duplicate by last-write-wins — the later group's value becomes the effective runtime weight with no warning emitted. The PoC declares `dense/kernel = 1.0` in Group[0] and `dense/kernel = 999.0` in Group[1]. The runtime resolves the duplicate and produces `predict([[1.0]]) = 999.0`. ## Differential | | `dense/kernel` | `predict([[1.0]])` | Warning | |---|---|---|---| | Group[0] first-declared value | 1.0 | 1.0 | — | | `@tensorflow/tfjs` v4.22.0 effective value | 999.0 | **999.0** | None | ## Requirements - Node.js >= 14 - `@tensorflow/tfjs` ^4.22.0 ```bash npm install ``` ## Reproduction Steps ```bash # Step 1: Generate the crafted model node create_model.js . # Step 2: Inspect declared duplicate weight values node inspect_model.js . # Step 3: Run the runtime differential (loads all groups — last-write-wins) node reproduce.js . ``` Or all steps at once: ```bash npm run poc ``` ## Expected Output **inspect_model.js (declared values across all groups):** ``` DECLARED_GROUP0_KERNEL=1 DECLARED_GROUP1_KERNEL=999 DUPLICATE_WEIGHT_NAME=dense/kernel ``` **reproduce.js (`@tensorflow/tfjs` v4.22.0 effective value after last-write-wins):** ``` RUNTIME_KERNEL=999 RUNTIME_OUTPUT=999 OUTPUT_FLIP_CONFIRMED=true WEIGHT_WARNING_EMITTED=false ``` ## Format Details `model.json` in the TF.js layers-model format contains a `weightsManifest` array. Each element is a group with `paths` (binary shard files) and `weights` (weight name/shape/dtype specs). The original design supports sharding: splitting a large weight set across multiple `.bin` files for parallel HTTP loading. The crafted model uses this structure with two groups declaring the same weight names: ```json "weightsManifest": [ { "paths": ["group0-shard1of1.bin"], "weights": [ { "name": "dense/kernel", "shape": [1, 1], "dtype": "float32" }, { "name": "dense/bias", "shape": [1], "dtype": "float32" } ] }, { "paths": ["group1-shard1of1.bin"], "weights": [ { "name": "dense/kernel", "shape": [1, 1], "dtype": "float32" }, { "name": "dense/bias", "shape": [1], "dtype": "float32" } ] } ] ``` - `group0-shard1of1.bin`: `dense/kernel = 1.0` (first-declared value) - `group1-shard1of1.bin`: `dense/kernel = 999.0` (effective runtime value after last-write-wins) ## Files | File | Description | |---|---| | `model_crafted.json` | Model with duplicate `dense/kernel` across two `weightsManifest` groups | | `group0-shard1of1.bin` | Group[0] binary: `dense/kernel=1.0` | | `group1-shard1of1.bin` | Group[1] binary: `dense/kernel=999.0` | | `create_model.js` | Generates the crafted model and shards | | `inspect_model.js` | Reports duplicate weight declarations across weightsManifest groups | | `reproduce.js` | Runs `@tensorflow/tfjs` and confirms effective value is 999.0 | | `expected_output.txt` | Expected output values | | `SHA256SUMS_T1.txt` | File integrity checksums |