You Wouldn't Run a Stranger's Script. You Download Their Model Weights Every Day.
Software supply chain security has been the industry’s obsession since SolarWinds. SBOMs, sigstore, dependency pinning, Dependabot alerts in every repo. And yet there’s one artifact class that slipped past the whole conversation: model weights. We download them like we’re cloning a repo, drop them onto a GPU box, and run them. Have you ever actually looked inside one of those files?
Worth saying up front: this isn’t a report on a fresh incident. Nothing new broke this month. It’s a walk through structural weaknesses that have been sitting in plain sight for years — and an argument for why the risk is quietly growing rather than shrinking.
A Model File Can Be Code, Not Data
Start with the fundamental misconception. Most engineers treat weights as a big pile of numbers — inert, like a JPEG or a CSV.
But PyTorch’s classic .bin, .pt, and .pth formats use Python’s pickle protocol. Pickle doesn’t store objects. It stores instructions for reconstructing objects — and those instructions are executable. Deserialize a pickle file and arbitrary Python can run before you ever see a tensor.
Which means torch.load() is functionally equivalent to piping a stranger’s script into your interpreter. PyTorch’s own docs say plainly: don’t load files from untrusted sources. How many people read that warning and actually stop?
This is exactly why Hugging Face pushed safetensors. The format holds tensor data and nothing else — no code path, no execution hook. The problem is adoption. A meaningful chunk of repos still ship legacy pickle files alongside the safe ones, and plenty of tooling defaults to whichever it finds first.
The Bigger Hole Isn’t Weights. It’s trust_remote_code.
There’s a door standing wider open than pickle: the trust_remote_code=True flag in Hugging Face Transformers.
Novel architectures often require it. Flip it on and the library imports and executes Python files straight from the model repo. An attacker doesn’t need to craft a malicious pickle payload. They just add a .py file.
The depressing part is how normalized this has become. You try to run a new model, hit an error, search for it, and the top Stack Overflow answer or GitHub issue reply says: add trust_remote_code=True. So you add it. It works. You move on. The moment a security control gets absorbed into routine troubleshooting, it has stopped being a control at all.
Trust Accumulates Without Verification
Map the classic supply chain playbook onto the model ecosystem and it maps cleanly.
Typosquatting. Register a repo one character off from a popular model. This has been run against npm and PyPI hundreds of times. There is no structural reason model hubs are immune — arguably less reason, since model names are longer and weirder, and people copy-paste them from blog posts.
Account takeover. Compromise a trusted org account and you can quietly swap files in a repo that tens of thousands of pipelines already reference by name. No new package to promote. The distribution is already built.
Fine-tuning chain poisoning. This one is specific to ML and the most underrated. The open model ecosystem is a graph: someone fine-tunes a base model, someone else merges two fine-tunes, someone quantizes the merge, someone else builds a LoRA on top. Poison any node and every descendant inherits it. And almost nobody traces their model’s lineage more than one hop back — the model card usually stops there anyway.
Eval Pipelines Are the Perfect Target
The sharpest risk concentrates in one place: model evaluation infrastructure.
Evaluation is, by definition, the act of pulling in a pile of low-trust models and running them. Whether you’re maintaining a public leaderboard or benchmarking candidate models internally, the workload is the same — download many untrusted artifacts, execute them.
And here’s the irony: eval runs on the most privileged hardware you own. Big GPU boxes with cloud credentials attached, mounted access to internal datasets, write permissions to artifact stores and result databases.
For an attacker, that combination is close to ideal. You ship a file capable of executing arbitrary code, and the target voluntarily downloads it onto a high-value machine and runs it. Better still, that machine is usually unsegmented. It reads as “the benchmarking box” on the org chart, which is exactly the kind of framing that keeps something out of a security review.
What Actually Helps
None of the fixes are exotic. Four habits cover most of the exposure.
Take safetensors only. If a repo offers a safetensors version, pin to it explicitly rather than letting the loader pick. If a repo offers nothing but pickle files in 2026, treat that as a signal in itself.
Pin the revision. Reference models by commit hash, not by tag or branch name. Pass the hash to the revision argument and you keep using the exact bytes you vetted, even if the upstream repo is compromised next week. This is the single highest-leverage line of code in the list.
Treat trust_remote_code as an exception, not a default. If you need it, actually open and read the Python files in that repo, then confine execution to an isolated container. Never as a reflex to make an error go away.
Isolate your eval environment. No production credentials on machines running unvetted models. Block outbound network, run in disposable containers, destroy them when the job finishes. Cattle, not pets — the standard applies here more than anywhere.
The Question Worth Asking
We scrutinize weekly download counts and last-commit dates before adding a 40KB npm package. Then we pull a 40GB model because someone on X said it was good. AI adoption has outrun the formation of security habits by roughly a full generation of tooling.
So: the models running in your pipeline right now — where did they come from, who fine-tuned them, and when did those files last change? If you can’t answer that in under a minute, you’ve found your blind spot.
Comments
Loading comments...