You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

TensorFlow.js userDefinedMetadata.signature.outputs output node selection hijack

Target: TensorFlow.js – Google
Route: huntr MFV
Format: GraphModel (.json + .bin)
Field: model.json β†’ userDefinedMetadata.signature.outputs


1. Summary

TensorFlow.js loadGraphModel() reads userDefinedMetadata.signature.outputs from model.json without validation and uses it to select which graph node becomes the model output. A model.json that contains an injected userDefinedMetadata.signature can redirect model.predict() to return the value of a different (intermediate) graph node β€” while the graph topology, weight binary, and weightsManifest remain identical. No warning or error is emitted.

Confirmed with actual TensorFlow.js runtime:
@tensorflow/tfjs-converter 4.22.0 + @tensorflow/tfjs-backend-cpu 4.22.0
tf.loadGraphModel() + model.predict(tf.tensor([3]))


2. Affected Product

  • Package: @tensorflow/tfjs-converter
  • Tested version: 4.22.0
  • Format: GraphModel β€” model.json + *.bin weight shard(s)
  • Not affected: LayersModel (@tensorflow/tfjs-layers) β€” does not consume userDefinedMetadata

3. Vulnerability Details

Vulnerable code path

graph_model.js lines 147–157 β€” userDefinedMetadata.signature fully overrides artifacts.signature:

let signature = this.artifacts.signature;
if (this.artifacts.userDefinedMetadata != null) {
    const metadata = this.artifacts.userDefinedMetadata;
    if (metadata.signature != null) {
        signature = metadata.signature;   // ← COMPLETE OVERRIDE, no validation
    }
}

operation_mapper.js β€” mapSignatureEntries() converts the injected signature to a node-name map:

function mapSignatureEntries(entries) {
    return Object.keys(entries || {}).reduce((prev, curr) => {
        prev[entries[curr].name] = curr;
        return prev;
    }, {});
}

transformGraph() then pushes the named node into graph.outputs[], overriding the default leaf-node selection.

graph_executor.js line 104: this._outputs = graph.outputs β€” the injected intermediate node becomes the model's output.

Attack

A single field addition to model.json:

"userDefinedMetadata": {
  "signature": {
    "outputs": {
      "output_0": { "name": "Mul" }
    }
  }
}

When name matches any node in modelTopology, model.predict() returns that node's value instead of the intended final output β€” silently.


4. Impact

  • model.predict() returns the tensor value of an intermediate graph node rather than the intended final output.
  • The graph topology, weight binary, and weightsManifest are unchanged.
  • No warning, exception, or log message is emitted.
  • A downstream consumer receives a wrong tensor value without any indication.

Scope: GraphModel only. LayersModel is not affected.


5. Proof of Concept

Graph (clean and mutant use identical topology)

Input (Placeholder, float32, shape [1])
  └─→ Mul  (Input Γ— Input = xΒ²)
        └─→ Add  (Mul + Mul = 2xΒ²)    ← default leaf output

For input x = 3: Mul = 9, Add = 18.

Run

npm install
node reproduce_tfjs_signature_outputs_flip.js

Output

clean  predict(3) -> 18  (outputNodes: ["Add"])
mutant predict(3) -> 9   (outputNodes: ["output_0"])

OUTPUT_TENSOR_VALUE_FLIP : true
OUTPUT_NODE_FLIP         : true
WARNING_EMITTED          : false
TFJS_SIGNATURE_OUTPUTS_FLIP_CONFIRMED

Artifact isolation check

node inspect_tfjs_signature_outputs_hash_matrix.js
modelTopology     : IDENTICAL
weightsManifest   : IDENTICAL
weight SHA256     : IDENTICAL
userDefinedMetadata clean  : undefined
userDefinedMetadata mutant : {"signature":{"outputs":{"output_0":{"name":"Mul"}}}}
CHANGED_FIELD_ONLY : userDefinedMetadata.signature.outputs
HASH_MATRIX_PASS

6. Evidence

  • evidence_runtime_results.json β€” actual runtime output values (ACTUAL_TFJS_RUNTIME tier)
  • evidence_reproducibility.json β€” 8/8 reproducibility (5x same-process + 3x subprocess)
  • evidence_hash_matrix.json β€” SHA256 isolation verification
  • evidence_distinctness_matrix.json β€” distinctness from 6 prior findings

7. Distinctness from Prior Findings

Root Field Format Consumer Effect Overlap
This finding userDefinedMetadata.signature.outputs TFJS GraphModel .json graph_model.js + operation_mapper.js Wrong intermediate node returned by predict() β€”
fc03a614 (SUBMITTED) weightsManifest[].weights[].name TFJS GraphModel .json io_utils.js decodeWeights() Wrong weight tensor values at correct output node NONE
TFLite SignatureDef FlatBuffer SignatureDef TFLite .tflite C++ Task Library Output key rename NONE
TFLite AssociatedFile FlatBuffer label file TFLite .tflite C++ Task Library Classifier Label string substitution NONE
TFLite NormalizationOptions FlatBuffer mean/std TFLite .tflite C++ Task Library preprocessor Input rescaled before inference NONE
OpenVINO rt_info labels IR XML rt_info OpenVINO .xml+.bin openvino-model-api Python Class label name substitution NONE

8. Non-Claims

  • No remote code execution (RCE)
  • No arbitrary code execution (ACE)
  • No memory corruption
  • No scanner bypass as primary claim
  • Not a LayersModel vulnerability
  • Does not affect graph topology
  • Does not affect weight shard bytes
  • Does not allow creation of graph nodes beyond existing modelTopology
  • Does not affect @tensorflow/tfjs-node in a manner distinct from pure-JS backend
  • Not a duplicate of fc03a614 (distinct field, mechanism, code path, and effect)

9. Expected Triage Objections and Responses

O1: "userDefinedMetadata is metadata, not a security boundary."
The TFJS loader uses userDefinedMetadata.signature to directly determine which graph node is evaluated and returned by model.predict(). This is a behavioral control, not a descriptive annotation. The runtime makes no distinction between "authoritative" and "advisory" fields.

O2: "The injected node must already exist in the graph."
Agreed. The constraint only means the attacker must reference a valid node name. Any node in modelTopology is a valid target. The attack does not require graph modification.

O3: "This is just model supply-chain manipulation."
The finding is that the userDefinedMetadata field β€” which is distinct from the weightsManifest and modelTopology fields β€” carries behavioral authority that is not validated or disclosed to the consumer.

O4: "Duplicate of the weightsManifest duplicate-name root (fc03a614)."
Field, consumer function, code path, and effect are all different. fc03a614 is about weight tensor value collision in decodeWeights(); this finding is about output node selection override in mapSignatureEntries() + transformGraph().

O5: "Duplicate of TFLite SignatureDef root."
Different format (TFLite FlatBuffer vs JSON), different consumer (C++ vs JavaScript), different runtime path, and different effect (key rename vs node value redirect).

O6: "Only the output key name changes, not the value."
model.predict() returns tensor value 9 (Mul) instead of 18 (Add) β€” confirmed by actual runtime execution. This is a value change, not a key rename.

O7: "Users should hash the full model.json."
The TFJS runtime provides no built-in model.json integrity mechanism and no warning when userDefinedMetadata.signature overrides inference behavior.

O8: "A model.json change is obvious if inspected."
The override is a JSON field that is not checked by any standard TFJS inspection API. model.outputNodes returns ["output_0"] (the signature key), not the underlying node name, making the redirect opaque to casual review.


10. Recommendation

Add schema validation for userDefinedMetadata.signature in graph_model.js before overriding artifacts.signature, or emit a warning when userDefinedMetadata.signature is present and alters the effective output node set.


11. References

  • graph_model.js lines 147–157 (signature override)
  • operation_mapper.js mapSignatureEntries() (output node map)
  • graph_executor.js line 104 (this._outputs = graph.outputs)
  • GHSA: 0 advisories for @tensorflow/tfjs-converter
  • OSV: 0 vulnerabilities for @tensorflow/tfjs-converter (npm)
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support