Skip to content

Lesson 3 of 10 · 6 min read

Unsafe Model Files: Pickle versus Safetensors

Free lessonReviewed By the AISecurityInterviews editorial team
Reading position
In this lesson

TL;DR: Python pickle files carry instructions the loader executes, so a model checkpoint in pickle form (traditional PyTorch .pt, .bin, and many older formats) can run arbitrary code when loaded. Safetensors is a data-only format (a JSON header describing tensors, then raw bytes) that cannot execute code on load and is fast to memory-map. Switching formats fixes code execution at load time; it does not address backdoored weights, custom code that some models require (trust_remote_code), malicious tokenizers or configs that pull code, or bugs in format parsers. Treat every downloaded model as untrusted input.

Before you read

Pick an answer before you read further. The lesson below explains it, and guessing first, right or wrong, helps the explanation stay with you.

  1. 1A data scientist downloads a fine-tuned model from a public hub and loads it in a notebook on a shared GPU server that has cloud credentials in its environment. The file is a pickle-based checkpoint. What is the risk?

Why pickle checkpoints are dangerous

Python's pickle format serialises object graphs by recording a small program that the unpickler runs to rebuild them, and objects can specify functions to call during reconstruction. Python's own documentation warns that unpickling untrusted data can execute arbitrary code. That is the general insecure deserialization problem.

PyTorch's traditional torch.save and torch.load used pickle for checkpoints, and much of the ecosystem inherited it. Downloading a model and loading it was, in effect, downloading and running a program from its author. Security researchers have repeatedly found malicious models on public model hubs that execute code on load.

PyTorch 2.6 (January 2025) changed torch.load to default to weights_only=True, which restricts unpickling to tensors and a small allowlist of types. That closes the obvious path for code written against current PyTorch, but older code, other libraries and explicit weights_only=False calls remain.

What safetensors changes

Safetensors, developed by Hugging Face, stores:

  1. An 8-byte length.
  2. A JSON header listing each tensor's name, data type, shape and byte offsets.
  3. The raw tensor bytes.

There is no code, no object construction and nothing for a loader to execute. The format also supports memory-mapping and lazy loading, which makes it faster for large models, one reason adoption has been broad. An external security audit by Trail of Bits was published in 2023.

LESSON · UNSAFE MODEL FILES: PICKLE VERSUS SAFETENSORS
What each control stops in a model intake pipeline

Protecting: Training and serving hosts

  1. 1

    1 · Safetensors only

    format

    • Stops: Code in a pickle checkpoint
  2. 2

    2 · No remote code

    loader policy

    • Stops: Malicious custom model code
  3. 3

    3 · Hash + signature check

    provenance

    • Stops: Tampered file on a mirror
  4. 4

    4 · Behavioural evaluation

    probabilistic

    • Stops: Backdoored weights
  5. 5

    5 · Isolated loading env

    containment

    • Stops: Code in a pickle checkpoint
    • Stops: Malicious custom model code
    • Stops: Exploit of a loader bug
Switch safetensors off: isolation still contains a malicious pickle. Switch isolation off: nothing else stops a loader exploit. Backdoors are only partly detectable at all.

The safetensors format, precisely

A safetensors file is an 8-byte little-endian length, a JSON header describing each tensor's name, type, shape and byte offsets, and then the raw bytes. The format's rules exist for security: the header is capped at 100 MB so a parser never processes an enormous JSON document, duplicate keys are not allowed, and the byte buffer cannot contain holes, which prevents polyglot files that are valid safetensors and something else at once. Trail of Bits audited the library in March 2023 for Hugging Face, EleutherAI and Stability AI and found no critical flaw leading to arbitrary code execution; the specification imprecisions it found were fixed.

Why scanning pickles is not enough

Model hubs scan uploads, and the scanners help, but their limits are documented by the hubs and by attackers:

  • What scanners look for. Hugging Face lists the imports a pickle would perform by walking its opcodes without executing them: the dangerous pattern is an opcode that imports a callable (GLOBAL or STACK_GLOBAL) followed by one that calls it (REDUCE). Its documentation calls the scan "not 100% foolproof".
  • Evasion through errors. In February 2025 ReversingLabs found two malicious models ("nullifAI") that used 7z compression instead of ZIP and a deliberately broken pickle stream with the payload at the start. Unpickling runs opcodes in order, so the payload executed before the stream broke; the scanner errored out and reported nothing. The lesson for any intake pipeline: a scan error is a failure, not an inconclusive pass.
  • Scanner bugs. The widely used picklescan tool had several 2025 CVEs, including bypasses through file-extension handling, a ZIP entry with a bad CRC that halted the scan, and submodule imports that evaded its exact-match list of unsafe globals; versions 0.0.31 and later fix them.
  • Scale. Protect AI reported in April 2025 that its scanner had examined 4.47 million model versions in 1.41 million Hugging Face repositories and flagged issues in about 51,700 models, roughly 3.7% of the repositories scanned. A public hub is not a vetted source.
  • The flag itself. PyTorch 2.6 (January 2025) made torch.load default to weights_only=True, but CVE-2025-32434 showed that mode could still be made to execute code in versions up to 2.5.1. Upgrade and restrict, and still load untrusted files in isolation.
LESSON · PICKLE VERSUS SAFETENSORS
A broken pickle and two intake policies

Scan error treated as inconclusive

7z archive, broken stream

Scanner errors, reports nothing

payload sits before the break

Loaded: payload runs first

Outcome, unsafe: Code execution on load

The evasion was the error itself.

Format allowlist, errors fail closed

Not safetensors or GGUF

Converted in a throwaway microVM

no network, no credentials

Any scan error rejects

Outcome, safe: Rejected or safely converted

Only data-only formats reach serving.

1/2

Lenient intake

Unpickling runs opcodes in order, so a payload before a deliberate error executes while the scanner gives up.

What safetensors does not fix

RiskWhy safetensors does not helpWhat does
Backdoored or poisoned weightsThe numbers themselves encode the behaviourProvenance, evaluation, red teaming; detection is limited
Custom model codeSome architectures require running Python shipped in the repository (trust_remote_code=True in Hugging Face Transformers)Review the code, pin a commit, or avoid such models
Malicious companion filesTokenizers, configs, preprocessing scripts and notebooks travel with weightsInspect everything you load, not just the weights file
Loader and parser bugsAny parser of untrusted bytes can have memory-safety bugs; other formats (for example GGUF) have had parser vulnerabilitiesPatch loaders; isolate loading
Tampering in transit or on mirrorsA modified safetensors file is still a valid safetensors fileHashes and signatures checked against trusted references

A safe model intake process

  1. Prefer safetensors, and convert legacy checkpoints in an isolated environment once, then use the converted file.
  2. Restrict loading: weights_only=True for any remaining PyTorch checkpoints; never enable remote code without reviewing and pinning it.
  3. Verify provenance: pin model versions by commit or hash, verify publisher signatures where available (model signing), and keep an AI bill of materials.
  4. Scan: model scanners flag pickle opcodes that import or call functions, and unexpected files.
  5. Isolate: load and evaluate new models in an environment without credentials, network egress or access to production data.
  6. Evaluate behaviour before deployment, knowing that backdoors can be designed to evade evaluation.

Misconceptions interviewers listen for

  • "weights_only=True makes torch.load safe." Not on PyTorch 2.5.1 and earlier; upgrade and still isolate.
  • "The hub scanned it, so it is clean." Scanners have been evaded and have had bypass bugs; hubs say so.
  • "A scanner error means inconclusive." It was the evasion technique; fail closed.
  • "Safetensors solves the model supply chain." It removes load-time code execution for that file; other formats, remote code, configuration files and backdoored weights remain.
  • "Keras safe_mode is a sandbox." CVE-2025-1550 executed code through a crafted configuration despite it.

What if · interview follow-ups

Hold your answer as the constraints change

You run the model intake pipeline for a platform where customers upload fine-tuned models and engineers pull open models from public hubs. The interviewer probes one file at a time.

  1. Start here

    What if an engineer argues a .bin checkpoint is fine because they load it with weights_only=True?

How this shows up in interviews

"Why are pickle-based model files dangerous, and what does safetensors fix and not fix?" is one of the most common AI supply chain questions. The strong answer explains pickle's execution model, describes safetensors' structure, and then spends equal time on the limits: backdoors, remote code, companion files and parsers. Mentioning weights_only and isolation shows practical experience.

Say it in 30 seconds: "Pickle checkpoints contain instructions the loader executes, so loading an untrusted model runs the author's code with your permissions. Safetensors is just a JSON header and raw tensor bytes, so loading can't run code, and it's faster. That fixes load-time code execution only: backdoored weights, models that need remote custom code, malicious tokenizers and parser bugs are untouched. So intake is safetensors plus weights_only, no unreviewed remote code, pinned hashes and signatures, scanning, isolated loading and behavioural evaluation."

Check yourself

Say or write your answer first, then reveal. Explaining it from memory, in your own words, is the skill an interviewer is checking.

  1. 1A model card says to load the model with trust_remote_code=True, and the weights are in safetensors format. Is it safe to load on a production host?

  2. 2Why convert a legacy pickle checkpoint to safetensors in an isolated environment rather than on a developer laptop?

  3. 3Which pickle opcodes are the dangerous pattern scanners look for?

  4. 4Why should an intake pipeline fail closed when a model scanner errors?

Practise it as an interviewer asks it

Related lessons