Instructions to use EnigmaConsultant/huntr-poc-modelscan-keras-vectorizedmap-lambda-carrier with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Keras
How to use EnigmaConsultant/huntr-poc-modelscan-keras-vectorizedmap-lambda-carrier with Keras:
# Available backend options are: "jax", "torch", "tensorflow". import os os.environ["KERAS_BACKEND"] = "jax" import keras model = keras.saving.load_model("hf://EnigmaConsultant/huntr-poc-modelscan-keras-vectorizedmap-lambda-carrier") - Notebooks
- Google Colab
- Kaggle
YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
modelscan Keras .keras RCE blindspot β __lambda__ carrier in a keras.ops.vectorized_map (VectorizedMap) operation
Severity: High (arbitrary code execution; scanner reports a fully clean bill)
Affected tool: modelscan 0.8.8 β KerasLambdaDetectScan (native .keras v3 zip). Also applies to the H5 path (H5LambdaDetectScan).
Victim loader: keras.models.load_model(path, safe_mode=False) (keras 3.15.0 / TF 2.21.0) β the documented path for loading Lambda-bearing models.
Category: ModelScan scanner-bypass on Keras .keras/.h5 (Model File Formats, in-scope).
Summary
modelscan flags a Keras model as dangerous only when a top-level config.layers[] entry has class_name == "Lambda". keras.ops.vectorized_map(fn, x) used inside a functional model serializes as a top-level layer entry whose class_name is "VectorizedMap" (a keras.src.ops.core.Operation), carrying the attacker's Python callable as a {"class_name":"__lambda__", ...} value in its function config field. Because the class name is not "Lambda", modelscan never inspects it and returns total_issues: 0, errors: [], scanned: [evil] β a genuine clean result. At load, VectorizedMap.from_config bare-deserializes that field (no module_objects, so no short-circuit) via serialization_lib.deserialize_keras_object β __lambda__ branch β python_utils.func_load β marshal.loads β a real FunctionType, which VectorizedMap.call then invokes (backend.core.vectorized_map(self.function, elements)) during model build/first inference = arbitrary code execution.
Root cause
- Scanner gap (both formats):
modelscan/scanners/keras/scan.py_get_keras_operator_namesβ onlylayer.get("class_name") == "Lambda"in top-levelconfig.layers.modelscan/scanners/h5/scan.py_get_keras_h5_operator_namesβ same Lambda-only top-level check.modelscan/settings.pyβunsafe_keras_operators = {"Lambda": "MEDIUM"}only."VectorizedMap"is not listed and sub-fields are never inspected.
- Exec sink (victim side, keras 3.15.0):
keras/src/ops/core.py:1163-1167βVectorizedMap.from_config:config["function"] = serialization_lib.deserialize_keras_object(config["function"])(bare, nomodule_objectsβ does not short-circuit__lambda__).keras/src/saving/serialization_lib.py:686βclass_name == "__lambda__"βpython_utils.func_load(inner_config["value"]).keras/src/utils/python_utils.pyfunc_loadβmarshal.loadsβtypes.FunctionType.keras/src/ops/core.py:1139βVectorizedMap.callβbackend.core.vectorized_map(self.function, elements)calls the reconstructed function.
Distinct from previously-filed Keras/modelscan carriers
- Not the
TextVectorizationstandardize/splitcarrier (different layer, different field). - Not the
"module"-key legacyβmodern deserializer fork on.h5activation (this needs no injected"module"key;VectorizedMap.from_configbare-deserializes natively, and the class is not aDense/activation layer). - Not the nested-Functional-submodel real-
Lambdacarrier (there is noLambdalayer anywhere βclass_nameis"VectorizedMap", a distinct built-in operation). - Not the
training_config/compile loss/metric blindspot (this is a forward-pass model op firing at build/inference, not a compile-time callable). - Among all
keras.opsoperations,VectorizedMapis the one that both stores its function inget_configand bare-deserializes + invokes it (Map/Scan/Cond/Switch/β¦ take functions as call-time args and do not round-trip a stored function through model config).
Reproduce
Env: pip install tensorflow keras 'modelscan==0.8.8', KERAS_BACKEND=tensorflow (Python 3.10β3.12).
python build_poc.py # writes evil_vectorizedmap.keras + control_lambda.keras
modelscan -p evil_vectorizedmap.keras # -> No issues found! (total_issues: 0, errors: [])
modelscan -p control_lambda.keras # -> 1 MEDIUM 'Lambda' (control: detection works)
python demo_load.py # load_model(safe_mode=False) + predict -> /tmp/VMAP_PWN.txt written
Verified output (modelscan 0.8.8, keras 3.15.0, TF 2.21.0)
evil_vectorizedmap.kerasβ modelscan JSON:total_issues: 0,total_issues_by_severityall 0,errors: [],scanned_files: ["evil_vectorizedmap.keras"](seemodelscan_evil_result.json). Top-level layerclass_names are["InputLayer", "VectorizedMap"]β no"Lambda".- Execution:
keras.models.load_model("evil_vectorizedmap.keras", safe_mode=False)fires the payload during load (functional trace) andpredict()completes normally (output shape(2,3)); marker/tmp/VMAP_PWN.txt=VMAP_RCE_1000(realos.system/id -u). - Control 1 (detection works):
control_lambda.keras(identical payload wrapped in a realLambda) β modelscantotal_issues: 1,MEDIUM,"Use of unsafe operator 'Lambda'"(seemodelscan_control_result.json). - Control 2 (exec gate): default
load_model(...)(safe_mode=True) raisesValueError: Requested the deserialization of a Python lambda ...and the marker is not written β payload only fires under the documentedsafe_mode=False.
Impact
ACE against any pipeline that scans an untrusted .keras (or .h5) model with modelscan β receiving a clean, 0-issue, file-marked-scanned result β and then loads it via the standard load_model(safe_mode=False). No victim-side custom class and no Lambda layer present, so it defeats modelscan's "look for Lambda layers" detection model with a benign-looking built-in Keras operation.
Files
build_poc.pyβ regenerates both models (payload is benign: writes/tmp/VMAP_PWN.txtwithid -u).evil_vectorizedmap.kerasβ malicious model (class_nameVectorizedMap, noLambda).control_lambda.kerasβ negative control (same payload as a realLambda; modelscan flags it).demo_load.pyβ loads the evil model and triggers the payload.modelscan_evil_result.json/modelscan_control_result.jsonβ captured scanner output.
Suggested fix
In both Keras scanners, recurse all layer/operation entries and normalize sub-fields, flagging any class_name == "__lambda__" anywhere in the config (and treat function-carrying operations like VectorizedMap as unsafe), rather than matching only a top-level class_name == "Lambda".
- Downloads last month
- 55