pragnyanramtha commited on
Commit
c7596ad
·
verified ·
1 Parent(s): 0c64391

Upload folder using huggingface_hub

Browse files
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()