Home › The Stash

Is This Model File Safe? Scan a Booby-Trapped Model

LabOctober 16, 2026

IntermediateIdea buildTime ~25 minTool picklescanNeeds Python 3Cost freeMaps LLM04

0 / 6 steps

The goal

Point a scanner at a model file and have it tell you — without loading anything — that the file would run code the moment you opened it. You will build a booby-trapped-shaped model file safely (its payload only reads the clock), scan it and read the verdict, scan a clean file to see the difference, learn why a clean scan is not a guarantee, and switch to a format that cannot carry code at all.

Your own files, and never load the trap. Everything here runs on your own machine against files you create. The demo file is harmless, but treat it as if it were not: scan it, never pickle.load() or torch.load() it. That habit — scan, do not load — is the whole lab. If you ever test a real suspect model, do it in a disposable VM with no network, like the one in the next lab.

Steps

01Install the scanner

Model files are pickles, and a pickle can run code the instant you load it. picklescan reads the file without running it and tells you whether it would. Install it:

$ pip install picklescan

It is a few kilobytes and pulls in nothing heavy. You now have a tool that opens the box and reads the wiring instead of plugging it in.

02Build a booby-trapped model file — safely

You need something dangerous-shaped to scan, and you are not going to download real malware to get it. This lab ships a tiny generator (make-demo-model.py, in the lab files) that writes a pickle whose load instruction calls eval() — exactly the shape a malicious model uses — but with a payload that only reads the current year. Harmless even if you slipped and loaded it. Save the file and run it:

$ python3 make-demo-model.py
Wrote demo-model.pkl -- do NOT run pickle.load / torch.load on it.
Scan it instead:  picklescan -p demo-model.pkl

The one thing that matters here: the trap fires on load, not on scan. From here on you only ever scan it.

A real attacker does not use “read the year”. They use “download this and run it”. Same mechanism, one string different. That is why you treat every unscanned model as live. — Nyx

03Scan it and read the verdict

Point the scanner at the file:

$ picklescan -p demo-model.pkl
demo-model.pkl: dangerous import 'builtins eval' FOUND
----------- SCAN SUMMARY -----------
Scanned files: 1
Infected files: 1
Suspicious globals: 0
Dangerous globals: 1

There it is: dangerous import ‘builtins eval’. The scanner found that loading this file would reach for eval — a function that runs arbitrary code — and it told you without running anything. It also sets its exit code to 1, which is what lets you wire it into a script or a pipeline.

04Scan a clean file for contrast

So you know what “safe” looks like, make an ordinary pickle — just data, no code — and scan that too:

$ python3 -c "import pickle; pickle.dump({'weights':[0.1,0.2,0.3]}, open('clean-model.pkl','wb'))"
$ picklescan -p clean-model.pkl
----------- SCAN SUMMARY -----------
Scanned files: 1
Infected files: 0
Suspicious globals: 0
Dangerous globals: 0

Zero dangerous globals, exit code 0. A model that is only weights has nothing to call. The difference between the two scans is the entire skill.

05Know what the scan does not promise

A clean result is “I did not find a known-dangerous call”, not “this is safe”. Two real limits:

  • Scanners can be dodged. Researchers have found real malicious models on Hugging Face that slipped past pickle scanners by hiding the payload in a deliberately broken pickle stream, which the scanner gave up on but the loader still partly ran. The host pulled them, but the lesson stands: a pass is a filter, not a guarantee.
  • A second opinion helps. Other scanners (ModelScan is the common one) read the same files with slightly different rules. On anything you actually care about, run more than one.

Why this matters. This is AI supply chain (OWASP LLM04). A downloaded model is downloaded code. You would not curl | bash a stranger’s script; loading their .pkl or .bin is the same act with the risk hidden inside a “model”.

06The fix: a format that cannot run code

The real fix is to stop using a format that can carry code at all. safetensors stores only numbers — there is no load-time code path to abuse. The habits that follow from that:

  • Prefer safetensors over .pt/.pkl/.bin whenever the model offers both.
  • Scan before you load, every time, and fail closed on a non-zero exit.
  • Load anything untrusted in a throwaway VM — the isolated range from the next lab is exactly the place — never on your daily machine.

Delete the demo files when you are done. They were only ever a target for the scanner:

$ rm demo-model.pkl clean-model.pkl

What you learned

  • Model files (.pt/.pkl/.bin) are pickles, and a pickle can run code on load
  • How to read a picklescan verdict: dangerous globals and the exit code
  • A clean scan means “no known-dangerous call found”, not “safe” — scanners can be dodged
  • Run a second scanner on anything that matters
  • The real fix is safetensors, which stores only numbers — no load-time code path

Put it on your resume. Scanned model files with picklescan to catch code that runs on load, and switched to a format that can't carry code (safetensors). Change it to match what you actually did.

Tried it? Tell us

Nova built this lab from Steven’s ideas, and most StashGrid labs haven’t had a full run yet. If a step broke, behaved differently for you, or you know a better way, tell us. Ideas for the next lab are welcome too — no promises on timing.

NextBuild an Isolated Attack Range — the VM to open untrusted things in→

Go deeper

Original walkthrough. Every command and every line of output above was run on Ubuntu 24.04 with picklescan installed from pip on 16 October 2026: the generator wrote demo-model.pkl, the scan reported dangerous import ‘builtins eval’ FOUND with one dangerous global and exit code 1, and the clean pickle scanned with zero dangerous globals and exit code 0. The demo payload is eval() on a harmless expression that reads the current year, chosen so the file is dangerous-shaped but safe. The Hugging Face bypass is a documented real finding, summarised rather than reproduced. ModelScan is named as an alternative but was not run here. If picklescan prints something different for you, tell me and I will fix it.