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.

ModelScan H5 RCE blindspot β€” __lambda__ carrier in a non-Lambda TextVectorization preprocessing field

Severity: High (arbitrary code execution; scanner false-clean) Affected tool: modelscan (latest, H5LambdaDetectScan) β€” scanned file is a Keras .h5; victim loader is tf.keras / keras.models.load_model(safe_mode=False) (keras 3.14.1). Category: ModelScan scanner-bypass on .h5/.keras (in-scope, Model File Formats).

Summary

modelscan's HDF5 scanner only treats a layer as dangerous when its class_name == "Lambda". A serialized Python callable ({"class_name":"__lambda__", ...}) planted inside a built-in, non-Lambda TextVectorization layer's standardize or split field is therefore never inspected β€” modelscan reports 0 issues, 0 skipped, 0 errors (a genuine clean bill). At load time, TextVectorization.from_config deserializes those fields through a path that honors __lambda__ β†’ marshal.loads β†’ arbitrary FunctionType, which is then invoked on the first predict()/adapt() = arbitrary code execution.

Root cause (scanner gap)

  • modelscan/scanners/h5/scan.py:105-130 (_get_keras_h5_operator_names) iterates only top-level config.layers[] and appends a finding solely when layer.get("class_name") == "Lambda".
  • modelscan/settings.py:43-45 β†’ unsafe_keras_operators = {"Lambda": "MEDIUM"} only. Every other class and every config sub-field is ignored.

Exec sink (victim side, keras 3.14.1)

  • keras/src/layers/preprocessing/text_vectorization.py:491-505 β€” from_config calls bare serialization_lib.deserialize_keras_object(config["standardize"]) / (config["split"]) with no module_objects.
  • keras/src/saving/serialization_lib.py:660-670 β€” routes class_name == "__lambda__" to python_utils.func_load when safe_mode=False.
  • keras/src/utils/python_utils.py:54-100 β€” func_load β†’ marshal.loads β†’ FunctionType.
  • text_vectorization.py:541-542 / :566-567 β€” _preprocess does if callable(self._standardize): inputs = self._standardize(inputs) (same for split) β†’ executes attacker bytecode.

Why this field and not the obvious ones: planting the same __lambda__ carrier in activation/constraint/initializer/regularizer does not work β€” those route through module_objects deserializers that short-circuit and return the bare class_name string. The TextVectorization.from_config path is distinct precisely because it calls deserialize_keras_object directly. (Control verified in the PoC: a Dense.activation carrier did not fire.)

Reproduce

python poc/poc_final.py (env: tensorflow 2.21.0, keras 3.14.1, h5py, modelscan). Builds evil_standardize.h5 and evil_split.h5 (6472 bytes each). For both:

  • [A1] ModelScan().scan() β†’ 0 issues / 0 skipped / 0 errors (scanner blind, file recorded as scanned).
  • [A2] keras.models.load_model(path, safe_mode=False) then model(tf.constant(["hello world"])) β†’ marker file written with real exfil (uid/host). With a vocabulary shipped in the config, predict() completes silently while the payload fires.

Impact

ACE on anyone who scans a malicious .h5 with modelscan (gets a clean result) and then loads + runs it via the standard load_model(safe_mode=False) + predict(). Defeats modelscan's core promise for a built-in layer with no victim-side custom class; two independent carrier fields (standardize, split). safe_mode=False is the standard/documented path for legacy .h5 (and Keras ignores safe_mode for .h5 per CVE-2025-1550/9905 anyway).

Dup-check

Clean. The dev.to "29 ways" article's only Keras/H5 bypass is Lambda-in-TimeDistributed (structurally different). modelscan docs describe H5 detection as "look for lambda layer" (Lambda-only). The Keras safe_mode-on-h5 CVEs are a different component (Keras honoring Lambda exec, not the scanner) and use actual Lambda layers. Distinct from our prior R1 h5 activation-field lambda (that field short-circuits via module_objects) and R2 modelscan H5 training_config blindspot (different sink). No public source ties a non-Lambda preprocessing-layer __lambda__ carrier to a modelscan H5 miss.

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