# Summary This report demonstrates a ModelScan scanner bypass for an MLflow `python_function` model artifact that stores its `python_model` as a gzip-compressed cloudpickle file. The submitted MLflow model contains an `MLmodel` metadata file whose `python_function` flavor points to `python_model.pkl.gz` and declares `python_model_compression: gzip`. In the tested MLflow source build (`3.14.1.dev0`, commit `6dcba5418220557ca2256ac4252cb6b58aad89b3`), `MLFLOW_ALLOW_PICKLE_DESERIALIZATION` defaults to `True`, so a normal `mlflow.pyfunc.load_model()` call decompresses `python_model.pkl.gz` and passes it to `cloudpickle.load()`. ModelScan `0.8.8` reports `0 issues` and `0 errors` for the MLflow model directory. It does not inspect the compressed pickle because its scanner routing relies on the final file suffix; `python_model.pkl.gz` is treated as unsupported `.gz` content and skipped. # Affected Format MLflow model artifact / `python_function` pyfunc flavor using compressed `python_model.pkl.gz`. # Security Impact A user or platform relying on ModelScan to identify unsafe model deserialization receives a clean issue/error result for this MLflow artifact, even though MLflow loads the compressed cloudpickle payload during model loading. The PoC payload is non-destructive. It only writes a marker file named `mlflow_compressed_pickle_marker.txt` in the current working directory containing: ```text MLFLOW_COMPRESSED_PICKLE_BYPASS ``` The control run sets `MLFLOW_ALLOW_PICKLE_DESERIALIZATION=false`, and in that mode MLflow blocks the load and the marker is not written. With the default environment, `mlflow.pyfunc.load_model()` loads the model and writes the marker. # Reproduction Steps 1. Clone or download the Hugging Face PoC repository. 2. Install dependencies: ```bash pip install -r requirements.txt ``` 3. Run the verifier: ```bash python verify_poc.py --model-dir . ``` Expected output includes: ```text ModelScan total issues: 0 ModelScan total errors: 0 ModelScan total scanned: 0 Skipped: python_model.pkl.gz - Model Scan did not scan file MLflow load with pickle gate disabled: MlflowException: Deserializing model using pickle is disallowed... Marker after disabled-gate load: False Default MLflow load: loaded Marker after default load: True Marker contents: MLFLOW_COMPRESSED_PICKLE_BYPASS ``` # Root Cause ModelScan does not understand MLflow's `MLmodel` metadata and does not use that metadata to discover that `python_model.pkl.gz` is a compressed pickle. Its extension-based routing sees only the final `.gz` suffix and skips the file, while MLflow uses `python_model_compression: gzip` from `MLmodel` to decompress and cloudpickle-load the payload. # Suggested Remediation ModelScan should add MLflow-aware scanning for `MLmodel` artifacts. For `python_function` flavor entries, it should inspect the `python_model` field and honor `python_model_compression` values such as `gzip`, `bzip2`, and `lzma`. After decompression, the underlying pickle/cloudpickle stream should be passed through the existing unsafe pickle global scanner. ModelScan should also report unsupported executable model components as issues or warnings rather than silently skipping them with a clean issue count.