Nothing is executed
Pickle streams are walked opcode by opcode with pickletools, simulating the stack and the memo. No pickle.load, no eval, no import. XML goes through defusedxml, so entity expansion is refused rather than performed.
Hayward / Open source · MIT
Hayward is a static security scanner for machine-learning model files. It reads PyTorch, pickle, joblib, GGUF, SafeTensors, ONNX and Keras, and Hugging Face configs, without executing them, and tells you whether loading a checkpoint would run code.
The problem
torch.load, joblib.load and numpy.load(allow_pickle=True) execute code from the file they read. That is not a bug. It is what pickle does. The file is named pytorch_model.bin, and the .bin is doing a lot of work in that sentence: it is a program, and loading it is running it, on your machine, with your credentials, as you.
Hayward reads the file first and tells you what the loader would have done.
How it works
Format is decided by magic bytes, then the file is read structurally. Nothing in it is executed, and a clean result means the file was examined, not that the parser gave up.
Pickle streams are walked opcode by opcode with pickletools, simulating the stack and the memo. No pickle.load, no eval, no import. XML goes through defusedxml, so entity expansion is refused rather than performed.
Deny lists lose: an attacker needs one callable nobody listed. Hayward resolves each call's arguments and convicts on the evidence. A URL, a shell-shaped command, or a host and port promote an unknown callable to HIGH, whatever it is called.
Native loaders allocate from numbers in a file's header. Hayward replays the container arithmetic for GGUF, SafeTensors and TFLite and reports a header that disagrees with the file, the integer-overflow class behind CVE-2025-53630 and CVE-2026-42627.
A payload hidden behind a deliberate parse error can scan cleaner than the plain one. In Hayward no parse attempt may lower suspicion or erase prior evidence, and a file it could not fully read is reported as unread, never passed as clean.
What it scans
Format comes from magic bytes, so a payload renamed weights.safetensors, or given no extension at all, does not walk past on the strength of its name.
PyTorch (zip, legacy and .ptl mobile), joblib, NumPy .npy / .npz, TorchServe .mar, NVIDIA NeMo, skops.
SafeTensors, GGUF and TFLite, checked for headers whose declared spans do not fit the file.
ONNX custom ops and external-data references, TensorFlow SavedModel, Keras Lambda layers (H5 and .keras), and PMML.
Hugging Face config.json and tokenizer_config.json: auto_map, trust_remote_code, and Jinja chat_template injection, all of which run on load.
PE, ELF and Mach-O magic anywhere in a model file. No serialisation format writes one.
Ways to run it
import hayward.guard makes torch.load and friends refuse a dangerous call as it runs.Signed verdicts
A scan is only as good as your trust in the scanner. Hayward writes a signed in-toto receipt for a scan, and the hayward-verify service publishes a signed verdict for popular Hugging Face repos at pinned commits.
hayward verify fetches the published verdict for a checkpoint before you download it, checks it was signed by the service's own workflow and signed recently, and lists every file the verdict does not cover. A pass means vouched for: no published verdict is not a pass, and a coverage gap is a fail.
What a clean result means
A clean report means Hayward read the files and recognised nothing dangerous in them. Not that the model is safe. It is built to stay quiet, because a gate that cries wolf gets switched off, and it reports the rooms it could not get into rather than calling them clean. Any accuracy figures we quote are self-measured and not independently reproducible; the harness runs against your own corpus.
Measured
On 222 benign files, 210 real Hugging Face models pinned by hash and 12 the harness generates, Hayward raised no alarms and warned on 3% of them, 7 files, while returning a verdict on every file. At its default gate it blocks no benign file. That quiet is the point: a gate that cries wolf gets switched off.
The honest other half: on attack sets other people wrote, Hayward is neither the loudest tool nor silent. At its default threshold it lets some PickleCloak chains through, and a lower threshold catches more, still with no benign file blocked. We build Hayward and wrote the benchmark harness, the figures are self-measured, and the attack sets were not held back from our own development. Both thresholds, the method and the limits are in the write-up.
Open source
Hayward installs with a single dependency and no model framework, runs on Python 3.10 and up, and hayward scan makes no outbound calls, so it runs on a laptop, in CI, or inside a regulated environment where nothing is allowed to leave the network. MIT licensed. The most useful thing you can send us is a file it gets wrong.