## Summary This report demonstrates a ModelScan scanner bypass for an HDF5/Keras-H5 model file containing an unsafe `TorchModuleWrapper` deserialization path. ModelScan `0.8.8` scans the submitted `.h5` file with `H5LambdaDetectScan` and reports `0 issues` and `0 errors`. The same H5 `model_config` contains a `keras.layers.TorchModuleWrapper` layer with a base64-encoded pickle payload in the `module` field. Keras itself treats this path as unsafe: default `safe_mode=True` blocks it. When unsafe deserialization is explicitly enabled with `safe_mode=False`, Keras reaches `torch.load(..., weights_only=False)` and the embedded payload writes a harmless marker file. ## Affected Format HDF5 / Keras H5 `.h5` ## Security Impact This is a scanner bypass for an unsafe HDF5/Keras-H5 deserialization construct. If a downstream workflow relies on ModelScan to decide whether a `.h5` model file is safe, this payload is reported clean. If the same model is later loaded in a workflow that enables unsafe Keras deserialization, the embedded `torch.load()` pickle path executes code during model loading. The PoC payload is intentionally non-destructive. It only writes: `h5_torch_wrapper_marker.txt` with contents: `H5_TORCH_WRAPPER_BYPASS` ## Reproduction Steps 1. Download or clone the public Hugging Face PoC repo. 2. Install dependencies: `pip install -r requirements.txt` 3. Run the verifier: `python verify_poc.py --payload poc_h5_torch_wrapper.h5` 4. Observe that ModelScan reports: `0 issues` `0 errors` `total_scanned: 1` 5. Observe that default Keras loading blocks the unsafe path: `Marker after safe_mode=True: False` 6. Observe that unsafe deserialization reaches the embedded `torch.load()` path and writes the marker: `Marker after safe_mode=False: True` `Marker contents: H5_TORCH_WRAPPER_BYPASS` ## Root Cause ModelScan's H5 scanner checks the H5 `model_config` for layers whose `class_name` is `Lambda`, but it does not detect other unsafe Keras deserialization constructs such as `keras.layers.TorchModuleWrapper`. Keras treats `TorchModuleWrapper` as unsafe because `TorchModuleWrapper.from_config()` can base64-decode the `module` field and pass it to: `torch.load(..., weights_only=False)` This can execute attacker-controlled pickle payloads when unsafe deserialization is enabled. ## Scope Note This is not a claim that default Keras loading executes the payload. Default `safe_mode=True` blocks the unsafe `TorchModuleWrapper` deserialization path. The reported issue is that ModelScan reports `0 issues` and `0 errors` for an HDF5/Keras-H5 file containing an unsafe construct that Keras itself treats as dangerous. ModelScan already flags `Lambda` layers in H5 `model_config`, but it does not flag `TorchModuleWrapper`. ## Evidence PoC file: `poc_h5_torch_wrapper.h5` Verification script: `verify_poc.py` Scanner output: `modelscan_result_h5_torch_wrapper.json` Tested versions: `modelscan==0.8.8` `h5py==3.16.0` `keras==3.15.0` `tensorflow==2.17.1` `torch==2.12.1+cpu` ## Suggested Remediation ModelScan should extend H5 `model_config` scanning beyond `Lambda` layers and flag unsafe Keras deserialization constructs that Keras itself protects with safe mode, including `keras.layers.TorchModuleWrapper`. For `TorchModuleWrapper`, ModelScan should inspect the `module` field, recognize it as a base64-encoded pickle / torch serialization payload, and either flag it directly or pass it through the existing unsafe pickle scanner.