Views
No views yet
.pt, payload behind the magic-number pickletouch /tmp/POC_PWNED_torch). For huntr.com Model File Format disclosure.scan_pytorch → scan_pickle_bytes(..., multiple_pickles=False)) reads only the first pickle stream of a .pt file. PyTorch's legacy serialization format (torch.save(obj, f, _use_new_zipfile_serialization=False)) is a sequence of separate concatenated pickle streams: pickle(MAGIC_NUMBER), pickle(PROTOCOL), pickle(sys_info), then pickle(payload), then storages.multiple_pickles=False stops after the first stream (the magic number), the malicious __reduce__ payload in a later stream is never parsed. ModelScan reports the file clean — total_issues: 0, errors: [], scanned: 1 (it believes it fully scanned a clean file). torch.load(f, weights_only=False) still executes the payload.evil_legacy.pt — the bypass: flat legacy .pt, payload in a later pickle stream. ModelScan: clean.evil.pt — control: identical payload in the modern zip format. ModelScan: flagged CRITICAL os.system.build.py — builds both.1pip install modelscan torch
2python build.py
3modelscan -p evil_legacy.pt # => "No issues found!", Total Issues 0 (BYPASS)
4modelscan -p evil.pt # => Total Issues 1, CRITICAL os.system (control, caught)
5python -c "import torch; torch.load('evil_legacy.pt', weights_only=False)" # marker /tmp/POC_PWNED_torch created_list_globals(..., multiple_pickles=False) → globals found: set() (what ModelScan does)_list_globals(..., multiple_pickles=True) → globals found: {('os','system')}torch.load(weights_only=False). torch ≥2.6 defaults weights_only=True, but weights_only=False is exactly the scenario ModelScan exists to defend, and remains common (e.g. NeMo forces it False; much existing code passes it). Tested: modelscan 0.8.6, torch 2.8.0, Python 3.9.scan_pytorch, scan with multiple_pickles=True for legacy-format .pt files (iterate every concatenated pickle stream), not just the first.