TensorFlow SavedModel pbtxt ModelScan Bypass PoC

This is a benign security PoC for a TensorFlow SavedModel scanner/runtime mismatch. The model contains a WriteFile TensorFlow op that writes only the marker string HUNTR_TF_SAVEDMODEL_PBTXT_MARKER to tf_savedmodel_marker.txt in the caller's chosen working directory.

Files

  • tf_savedmodel_pbtxt_writefile/ - TensorFlow SavedModel using saved_model.pbtxt instead of saved_model.pb.
  • verify_poc.py - loads the SavedModel and calls the serving_default signature.
  • inspect_savedmodel_ops.py - parses saved_model.pb or saved_model.pbtxt and lists graph/function ops.
  • run_modelscan_api.py - runs ModelScan through its Python API so skipped files are visible in JSON.
  • build_poc.py - regenerates the control .pb model and the .pbtxt variant.
  • results/ - captured runtime, scanner, op-inspection, version, and hash output.
  • SHA256SUMS.txt - SHA256 manifest.

Why This Matters

TensorFlow documents SavedModel as a directory containing a complete TensorFlow program that can be loaded with tf.saved_model.load. TensorFlow's loader source checks both saved_model.pb and saved_model.pbtxt.

ModelScan 0.8.8 flags a binary saved_model.pb containing WriteFile as HIGH severity, but its default TensorFlow routing maps only .pb files to the SavedModel scanner. The same SavedModel program encoded as saved_model.pbtxt is accepted by TensorFlow 2.21.0 and executes the marker-write op, while ModelScan skips saved_model.pbtxt as unsupported.

Primary references:

Reproduction

Use Python 3.12. The captured run used:

python -m venv .venv
.venv/Scripts/python -m pip install -r requirements.txt

Runtime impact:

.venv/Scripts/python verify_poc.py tf_savedmodel_pbtxt_writefile --workdir runtime_check

Expected result:

  • TensorFlow 2.21.0 loads tf_savedmodel_pbtxt_writefile/.
  • serving_default returns wrote:HUNTR_TF_SAVEDMODEL_PBTXT_MARKER.
  • runtime_check/tf_savedmodel_marker.txt is created with HUNTR_TF_SAVEDMODEL_PBTXT_MARKER.

Inspect the embedded TensorFlow ops:

.venv/Scripts/python inspect_savedmodel_ops.py tf_savedmodel_pbtxt_writefile

Expected result includes:

{
  "source_file": ".../tf_savedmodel_pbtxt_writefile/saved_model.pbtxt",
  "contains_writefile": true
}

Scanner behavior:

.venv/Scripts/modelscan -p tf_savedmodel_pbtxt_writefile -r json
.venv/Scripts/python run_modelscan_api.py tf_savedmodel_pbtxt_writefile --out modelscan_api_pbtxt.json

Expected ModelScan 0.8.8 API result:

  • total_issues: 0
  • scanned_files: ["fingerprint.pb"]
  • skipped file includes saved_model.pbtxt with SCAN_NOT_SUPPORTED

Control behavior from results/modelscan_api_pb_control.json:

  • The same model graph stored as saved_model.pb is flagged as HIGH.
  • Issue: Use of unsafe operator 'WriteFile' from module 'Tensorflow'

Captured Evidence

Versions:

{
  "python": "3.12.12",
  "tensorflow": "2.21.0",
  "modelscan": "0.8.8"
}

Key SHA256:

472ee8a1c76ff29d49f77222dfc716f27d047d8dcd3e5b8bd6e66f578f36a841  tf_savedmodel_pbtxt_writefile/saved_model.pbtxt

See SHA256SUMS.txt for the full manifest.

Impact and Limitations

Impact: scanner bypass for a TensorFlow SavedModel program that contains a filesystem-writing op. A service that treats a zero-issue ModelScan result as safe, then loads and invokes the model, can execute model-carried file I/O.

Limitations: this PoC is not arbitrary code execution. It writes a local marker file only when the exported inference signature is called. It does not read credentials, delete files, persist, or contact a network service.

Mitigations

  • Treat saved_model.pbtxt as a TensorFlow SavedModel program and scan it with the same unsafe-op rules as saved_model.pb.
  • Prefer format detection based on SavedModel directory structure and loader-supported filenames, not only extension routing.
  • Surface skipped files in CLI JSON output by default when the skipped file is a loader-supported model program.
Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support