ModelScan reads model files for unsafe code before you load them
Protection against Model Serialization Attacks
At a glance
- What is it?
- modelscan is a Python CLI from Protect AI that inspects serialized machine learning files for malicious code without executing them, covering Pickle, H5 and SavedModel formats. It suits teams moving models between environments, and it ships as a command you run on a path rather than a service.
- Who is it for?
- Adopt modelscan whenever a model file arrives from outside your trust boundary, from a colleague, an artifact store or a public repository, since loading a Pickle-based model runs the code inside it. Skip it as your only control if your pipeline also exchanges formats the tool does not cover, and do not treat a clean scan as clearance for a model whose bytes you cannot explain.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 26 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Loading a model can execute the code inside it
The threat ModelScan addresses is the Model Serialization Attack, described in the project documentation as malicious code added to the contents of a model during serialization, before distribution, framed as a modern version of the Trojan Horse.
The mechanism is the save and load path of a framework. When you call `model = torch.load(PATH)`, PyTorch opens the file and begins running the code within it, so the exploit executes the moment the model is loaded rather than later when the model is used. The same applies to any framework that deserialises untrusted bytes.
The consequences the documentation lists are credential theft, meaning cloud credentials for reading and writing data in your environment, data theft of the request sent to the model, data poisoning of the data returned after the task, and model poisoning, which alters the results of the model itself. Working examples live in the notebooks folder of the repository.
Byte-at-a-time inspection instead of deserialisation
The obvious way to check a model is to load it, and that is exactly the behaviour the tool exists to avoid. According to the documentation, ModelScan reads the file one byte at a time, much like reading a string, and looks for code signatures that are unsafe. The file is never deserialised, so nothing in it gets the chance to run.
The project states that this keeps the scan fast, on the order of the time the computer needs to read the total file size from disk, which it puts at seconds in most cases. The trade-off is inherent to the approach: matching against code signatures is a static, pattern-based judgement rather than a semantic analysis of what the payload would do if it ran.
Findings are ranked rather than returned as a flat yes or no. The documentation lists four severity levels, CRITICAL, HIGH, MEDIUM and LOW, so a pipeline can fail a build on the worst findings while still surfacing the rest to a human. The repository also ships a flow chart image, model_scan_flow_chart.png, describing the scan path.
Installing the CLI and scanning your first model
Installation is a single pip command, and the package is published on PyPI under the name modelscan:
pip install modelscanThe command is then pointed at a model file path:
modelscan -p /path/to/model_file.pklThe `-p` flag takes the path of the file to scan, and the example in the documentation uses a `.pkl` file, which is the Pickle-based format produced by `torch.save()`. Because the console entry point is defined in pyproject.toml as the entry point `modelscan.cli:main`, the same name is used whether the package is installed from a wheel or run from a source checkout.
What you should see is the severity-ranked report for the file you named. The documentation gives no sample output block, so the exact wording of the report is not specified, and the README does not document a machine-readable output format or a specific exit code for a failed scan, which is worth checking in your own pipeline before you rely on the exit status.
Format coverage: Pickle, H5 and SavedModel
ModelScan is described as the first model scanning tool to support multiple model formats. The formats named in the project documentation are H5, Pickle and SavedModel, and the stated intent is protection when using PyTorch, TensorFlow, Keras, Sklearn and XGBoost, with more support planned.
The support table maps each library to the API that produces the file, the serialization format, and whether ModelScan covers it. PyTorch pairs `torch.save()` and `torch.load()` with the Pickle format and is marked as supported. TensorFlow pairs `tf.saved_model.save()` with the Protocol Buffer format and is also marked as supported. Keras appears with the `keras.models.save(save_format= 'h5')` API and an H5 format entry.
Coverage is per library API and format, so the question to ask for any given pipeline is which of those rows describes the artifact you actually receive. A model saved in a format outside the list gets no verdict from this tool, and a library marked supported says nothing about a specific export option you happen to use.
Python version limits and optional extras in pyproject.toml
The package metadata constrains the runtime. The dependency block declares `python = ">=3.10,<3.13"`, so the tool runs on Python 3.10, 3.11 and 3.12, and a newer interpreter is out of range.
Framework support is optional rather than bundled. TensorFlow at `^2.17` and h5py at `^3.9.0` are both marked optional, and the extras section exposes them as `tensorflow` and `h5py`, which means the default install is deliberately small. The mandatory dependencies are click for the command line, numpy, rich for terminal output and tomlkit.
The build configuration is worth noting for anyone tracking versions. The build system requires poetry-core and poetry-dynamic-versioning, and the dynamic versioning section has `enable = true`, so version numbers are derived from the git tag rather than typed into pyproject.toml, which still shows `version = "0.0.0"` as a placeholder. That is why the published tags, v0.8.6, v0.8.7 and v0.8.8, carry the real numbers.
Development commands live in the Makefile
The repository is set up around Poetry, and the Makefile is the entry point for every workflow task. The default goal is help, and each target carries a short description comment so the list can be printed with `make help`.
For contributors, `install-dev` installs dev, test and extra dependencies and then installs the pre-commit hooks:
poetry install --with dev --with test --extras "tensorflow h5py"
pre-commit installTests run through pytest against the tests directory, and a coverage variant writes an XML report:
poetry run pytest tests/
poetry run pytest --cov=modelscan --cov-report xml:cov.xml tests/Static analysis has its own targets. `bandit` runs SAST scanning with the configuration in pyproject.toml, `mypy` runs type checking in strict mode with `--ignore-missing-imports --check-untyped-defs`, and `black` formats the tree. The bandit configuration excludes the tests and notebooks directories, and mypy excludes notebooks as well, which keeps the notebook examples free of the strict typing rules the package source is held to.
Benign embedded code and the limits of a signature match
A scanner that finds unsafe code will sometimes find code that is not an attack, and the project documentation says so plainly. It notes that code may be embedded in a model to make things easier to reproduce as a data scientist, and that this still opens you up for attack, then tells you to use your discretion about whether that suits your workload.
That is the core limitation to plan around. The scanner matches code signatures in bytes, so it cannot tell an intentional research script from an exfiltration payload when both are present in the file. There is no documented allowlist mechanism, no quarantine workflow, and no documented way to record a decision about a specific finding in the open-source tool.
Two boundaries follow. First, the format list is closed: a file in a format outside H5, Pickle and SavedModel receives no analysis. Second, the Python ceiling means a runner on 3.13 or later cannot install the package at all. The enterprise product Guardian is pointed at in the documentation as the way to add automatic format detection, enforced requirements for Hugging Face models, CI/CD integration and an audit trail, none of which the open-source CLI offers.
Static analysis of source code is a different job
The comparison that matters for a security review is with static analysis of Python source. The repository itself runs bandit as SAST scanning over its own code, and bandit inspects source files for risky constructs. ModelScan works on a different input: a serialized artifact whose contents are not Python source but pickle opcodes, and it never imports or executes the file.
That difference is why source scanners are not a substitute. A source scanner sees the code a developer typed; ModelScan sees the bytes that were written to disk, including anything that was embedded during a pipeline run. A model produced by an automated pipeline can carry a payload that exists in no repository file, and only a scan of the artifact will show it.
Against the commercial product the difference is scope rather than technique. Guardian is described as adding broader model support, automatic model format detection, requirement enforcement before Hugging Face models enter an environment, pipeline integration and a record of all scans. The open-source ModelScan is a single command on a path, with severity ranking and no enforcement layer, which keeps it free to run in any CI job that can install a Python package.
Editorial conclusion
Adopt modelscan whenever a model file arrives from outside your trust boundary, from a colleague, an artifact store or a public repository, since loading a Pickle-based model runs the code inside it. Skip it as your only control if your pipeline also exchanges formats the tool does not cover, and do not treat a clean scan as clearance for a model whose bytes you cannot explain. Before wiring it into CI, check that the runner satisfies the Python >=3.10,<3.13 constraint in pyproject.toml and that the extras you need are installed, since TensorFlow and h5py support are declared as optional.
Frequently asked questions
What is ModelScan and what does it detect?
ModelScan is an open source CLI from Protect AI that scans model files for unsafe code, covering H5, Pickle and SavedModel formats. It reads the file byte by byte looking for code signatures and does not execute or load the model.
How do I install modelscan and scan a model file?
Run `pip install modelscan`, then point the command at a file path with `modelscan -p /path/to/model_file.pkl`. The `-p` flag supplies the path of the model file to scan.
Which machine learning frameworks does ModelScan support?
The project documentation names PyTorch, TensorFlow, Keras, Sklearn and XGBoost, with the supported serialization formats being Pickle, Protocol Buffer and H5. The support table marks PyTorch and TensorFlow as supported and lists the save API for each library.
What severity levels does ModelScan report?
The documentation lists four levels, CRITICAL, HIGH, MEDIUM and LOW. Findings are reported as unsafe code ranked by those levels rather than as a single pass or fail verdict.
What Python version does modelscan support?
The package metadata in pyproject.toml declares `python = ">=3.10,<3.13"`, so Python 3.10 through 3.12 are in range. TensorFlow and h5py are optional extras rather than default dependencies.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/protectai-modelscan)Was this useful?