AI SeedbankHelp preserve open and free AI for humanity's future

Can model files be malicious?

Last updated: 2026-09-01

Yes, some of them can. A pytorch_model.bin, .pt, .pth, or .ckpt file is a Python pickle, and a pickle is a program that happens to live in a data file: loading one means running the instructions inside it, so a file built by someone hostile can execute code on your machine the moment you call torch.load. A .safetensors file cannot do that. The format has no execution path at all.

So the answer has two halves, decided by different people. The format decides whether the file is able to carry code. The publisher decides whether it does. The rest of this page takes them in order: the mechanics, the guarantees, a row-by-row threat table, the incidents, and a checklist that ends in checking the bytes you actually have.

One thing this page does not cover: poisoned training data, where the model was built badly rather than weaponized as a file. That is a different threat with different fixes.

The short answer

Five file families cover almost everything you will download, and they answer the question in the title differently.

  • Pickle family: pytorch_model.bin, .pt, .pth, .ckpt. Yes, code runs at load. These files are instruction streams and the loader obeys them.
  • Safetensors. No. Tensor data plus a JSON header, nothing to execute. The risk moves to provenance and tampering.
  • GGUF. No code in the file, but it carries a chat template your runtime renders, and most GGUF files are third-party repacks rather than the publisher's own release.
  • Repository Python, the modeling files. Real code. Transformers runs it only when you pass trust_remote_code=True, so the decision is yours to make.
  • Config and tokenizer files. Parsed data. Low risk, not zero, because a malformed file attacks the parser that reads it.

Two questions sit underneath all of it, and they have different answers. Format answers "could this file hurt me". Provenance answers "did anyone make it hurt me". A pickle from a release you can tie to the lab that trained the model and a clean-looking pickle from a stranger are the same format on opposite ends of the risk scale, and only one of those answers is printed on the file.

Why a pickle can run code when you load it

A pickle is not a container the way a JPEG is. It is a sequence of instructions for a small stack machine, and pickle.load or torch.load is the interpreter. The file says push this, import that, call this, and the loader obeys, in order, with your permissions and your network.

Most of those instructions are boring: they push strings and numbers and rebuild dictionaries. Two kinds are not. GLOBAL and STACK_GLOBAL import a Python object by name, and REDUCE calls whatever sits on the stack with the arguments underneath it. Hugging Face's security documentation on pickles names exactly these three as the instructions that pose a threat: the GLOBAL pair imports, REDUCE calls.

An attacker needs nothing beyond that. A stream that imports a builtin which executes strings, then REDUCE on it, and the file has run as you. That is the whole trick, which is why the format comparison is not cosmetic.

You can read a pickle without running it. pickletools.dis prints the instruction stream as text and executes nothing, and that same read-without-execute trick is what the Hub's scanner builds on. Here is the disassembly of a pickle containing nothing but a text string:

    0: \x80 PROTO      4
    2: \x95 FRAME      48
   11: \x8c SHORT_BINUNICODE 'data I want to share with a friend'
   57: \x94 MEMOIZE    (as 0)
   58: .    STOP

Every pickle looks like that: a short list of instructions, most of them harmless. This page stops short of showing a weaponized stream, because the difference is a few lines and you do not need them to defend yourself. What you need is the habit: a pickle from a stranger is a program you are about to run as yourself.

The design reason explains why this area gets warnings rather than fixes. Pickle serializes live Python objects, including instances of classes it has never seen, and rebuilding such an object requires the loader to call code. Python's own documentation at docs.python.org says it plainly: the pickle module is not secure, and you should only unpickle data you trust.

What safetensors guarantees, and what it does not

Safetensors replaced the program with a map. The format's README describes it in three parts: 8 bytes holding an unsigned little-endian integer with the header size, then that many bytes of JSON naming each tensor with its dtype, shape, and offsets, then the raw byte buffer and nothing else. The header is capped at 100 MB to stop anyone shipping absurd JSON, and the buffer must be fully indexed with no holes, which the README notes "prevents the creation of polyglot files", the two-files-in-one trick that hides a second payload inside a file that also parses as something innocent.

Loading is a memory map rather than a reconstruction. There is no moment at which a loader interprets instructions out of the file, and that is the property this whole argument rests on.

The format has been audited. Trail of Bits reviewed it and Hugging Face published the results on 2023-05-23 with the headline finding: "No critical security flaw leading to arbitrary code execution was found." The audit did turn up missing validation that allowed polyglot files, and that was fixed, which is where the no-holes rule and the header cap earn their keep. After the audit, Hugging Face, EleutherAI, and Stability AI all said they were moving to it as the default weight format.

What the format does not guarantee is everything outside the file. It does not tell you who wrote the bytes, or whether the weights were tampered with before publication. Tampered weights need no code execution to hurt you: a model that is quietly worse, or wrong in a direction someone chose, is a perfectly valid safetensors file. Trail of Bits showed the sharper version in June 2024, an attack that rewrites the model after load instead of running a shell. Safetensors does not check licenses either, which is a separate question from safety.

A safe format is not a safe file. The guarantee belongs to the parser, not to the name. A pickle renamed to .safetensors is still a pickle, and it only becomes harmless when nothing ever hands it to a pickle loader. Trust the bytes you can check, not the extension they arrived under.

Threat model by file type

The honest version of this answer is row by row, not one verdict for "model files".

FileWhat it really isCan it run code at loadWhat checking it looks like
pytorch_model.bin, .pt, .pth, .ckptA Python pickle: an instruction stream for the unpicklerYes. GLOBAL and STACK_GLOBAL import, REDUCE calls, so code runs inside torch.loadScan with Picklescan, read the stream with pickletools.dis, load only from publishers you trust
.safetensorsTensor bytes plus a JSON header, indexed by offsetNo. There is nothing in the format for a loader to executeCheck the digest against a value from a source you trust. Provenance is the whole question
config.json, tokenizer files, vocabulariesParsed data: JSON and textNo execution path, but a malformed file attacks the parser reading it, so keep the library currentTreat a repository whose configs will not parse as a bad sign and stop there
modeling files and other repository PythonSource code shipped alongside the weightsYes, if you opt in: transformers runs it only when you pass trust_remote_code=TrueRead it. This is a code review decision, not a checkbox
.ggufTensor data plus a standardized metadata set, including the chat templateNo. Not a pickle, no instructions to carry out. Your runtime renders the template, so the file still shapes behaviorPrefer repacks tied to a named source, and check the digest when one is published
.zip and .7z archivesA wrapper around whatever is really insideThe wrapper does not execute, but a malformed container can make a scanner skip the file, which is how nullifAI got past PicklescanUnpack first, then scan and inspect what came out

Two rows deserve a second look. The archive row is the exact route a real campaign used to defeat a scanner in 2025, covered below. The repository Python row is the one people forget: the weights are data, but the repo ships code beside them, and the switch that runs it sits in your script. The Hub's GGUF documentation is the source for that format's data-plus-metadata shape, and it also names safetensors a recommended format.

When it actually happened

None of this is hypothetical. Named campaigns, disclosed research, and two CVEs set the floor for how seriously to take the pickle row.

February 2024: about 100 malicious models on the Hub. JFrog reported on 2024-02-27 that roughly 100 models on Hugging Face carried pickle payloads, and that one of them, baller423/goober2, opened a reverse shell on load. Their writeup records the platform's response at the time: it "doesn't outright block or restrict them from being downloaded, but rather marks them as 'unsafe'". Outcome: flagged and named, still downloadable.

February 2024: the conversion service itself. HiddenLayer's Silent Sabotage research showed that a malicious pickle loaded by torch.load inside the Hub's safetensors conversion service could take over the bot that commits converted files and persist across restarts, turning one bad upload into tampering inside other repositories. HiddenLayer disclosed to Hugging Face before publishing. Outcome: a fix, and a reminder that the attack surface is the loading step, wherever it runs.

January 2025: nullifAI. ReversingLabs reported on 2025-01-20 two models whose pickles were deliberately malformed, built as 7z containers where the scanner expected ZIP, so Picklescan's validation failed and the scan was skipped instead of the file being flagged. The payload opened a reverse shell to a hardcoded address. Outcome: both models removed in under 24 hours, and Picklescan patched to scan broken pickles rather than skip them.

2025 to 2026: weights_only=True bypassed twice. PyTorch made the restrictive flag the torch.load default in 2.6.0, released 2025-01-29, calling it "an important security improvement measure". CVE-2025-32434, published 2025-04-17 with a CVSS 4.0 score of 9.3 covers everything below 2.6.0: "When loading model using torch.load with weights_only=True, it can still achieve RCE". CVE-2026-24747, published 2026-01-26 and rated High 8.8 covers everything below 2.10.0: the restricted unpickler failed to validate pickle opcodes and storage metadata, so a crafted checkpoint could corrupt heap memory. Outcome: patched in 2.6.0 and 2.10.0 respectively.

You will still see CVE-2025-1990 cited for the first of those. Its CNA rejected the record, and the two advisories above are the real ones.

December 2025: the scanner had bugs too. JFrog published three zero-day Picklescan bypasses on 2025-12-02: a CRC error in the container disables ZIP scanning, and the unsafe-import checks could be beaten through subclasses. The fixes had already landed in version 0.0.31 on 2025-09-02. Outcome: patched, and the lesson is that a scanner is software with its own parse of a hostile format.

What the Hub checks, and what it does not

Hugging Face scans every file at every commit. The security documentation lists malware scanning through ClamAV, a pickle import scan, and secrets scanning, plus third-party scanners from Protect AI and JFrog. The pickle scan reads the instruction stream without executing it, the same trick as pickletools.dis, and prints the list of imports beside each file with the suspicious ones highlighted.

None of that is a block. A flagged file draws a warning and advice to the repository owner to remove it, and the pickle scanning docs carry their own disclaimer that it "is not 100% foolproof". The JFrog incident above is the practical shape of that policy: marked unsafe, still downloadable.

Scale matters here too. Pickle is not a legacy corner. A 2025 study of the Hub found that 44.9 percent of the popular models it covered still ship pickle, that restrictive loaders fail outright on 15 percent of them, and that scanners show both false positives and false negatives. That is one paper's measurement, not a count of anything on this site. The practical reading: scanning is a filter, and the last check on a file you intend to load is yours.

This page stops at the artifact. Whether a host's accounts, repository naming, and review process deserve your trust is the platform question rather than the file question: are Hugging Face models safe.

Defense checklist

Six habits cover the whole table, in the order you meet them.

  1. Prefer safetensors, from the model's own organization. When a publisher ships both formats, the choice costs you nothing and removes the execution path outright. It is the cheapest risk reduction on this list.
  2. Treat every pickle as a program. If you have to load a .bin or .pt from someone you do not know, scan it with Picklescan and read the stream with pickletools.dis before it gets near torch.load. If you have a container or a throwaway machine with no credentials in it, load it there.
  3. Keep PyTorch current. weights_only=True is a real improvement and it has been bypassed twice, with the fixes landing in 2.6.0 and 2.10.0. An old install quietly reopens the hole.
  4. Make trust_remote_code=True a code review, not a click. The modeling file in a repository is source code that runs in your process. Read it or refuse it.
  5. Check the whole repository, not just the weights. Configs, tokenizer files, and archives are part of what you downloaded, and the archive row in the table above exists because a wrapper can defeat a scanner.
  6. Verify the bytes you ended up with. Whatever the format, compare the file against a digest from a source you trust before you load it. The command walkthrough, for a single file or a whole download, is in the guide to verifying a model download.

For GGUF, prefer a repack you can tie to a named source and a published digest, and remember that its chat template is rendered by your runtime, so the file influences behavior without executing anything. If you pull GGUF weights from huggingface.co into ollama, that flow and its quirks are covered in the ollama pull guide. Help choosing between formats is on the help page.

What we do about it at AI Seedbank

This archive takes the format side and the provenance side separately, and claims only one of them.

On format: the archive mirrors upstream files verbatim, and where a publisher ships .safetensors, those files are the weights of record. Nothing is re-serialized, re-quantized, or repacked on the way in. GGUF files are third-party repacks, and unless a GGUF is listed in the signed manifest, it did not come from us.

On provenance: every model's per-file digests ride in torrents.json, a manifest covered by a minisign signature you check offline. The methods are labeled exactly as they were computed: sha256 for LFS files, sha1+size for git blobs. One command proves the list came from the maintainers rather than from whatever served you the bytes, and the verify page is that walkthrough.

What that does not do is the honest limit, stated the way the verify page states it: verification does not audit model behavior. A signed digest proves you have the bytes upstream published. It does not prove those bytes are a good model, a safe model, or a model that does what its card claims.

The distribution argument is the same one level up: a swarm holds the bytes the manifest signed, and the infohash names that content independently of any web page that might change. The mechanics are in the AI model torrents guide and the magnet links and infohashes guide. Models in the classes discussed here: Qwen_Qwen3-4B, Qwen_Qwen2.5-7B-Instruct, NousResearch_Hermes-3-Llama-3.1-8B, and black-forest-labs_FLUX.2-klein-4B, all in the catalog.

Format removes the execution path. Provenance covers everything the format cannot. Neither substitutes for the other, and a model file that holds up under both is the only kind worth loading.

Frequently asked questions

Can a safetensors file contain malware?

Not in the way a pickle can. Safetensors holds tensor bytes plus a JSON header, and the format has no instructions for a loader to carry out, so there is no code-execution path for malware to use. That is not the same as a safe file. Someone can hand you tampered weights in perfectly valid safetensors, and a pickle renamed to .safetensors is still a pickle. A safe format is not a safe file: provenance and tampering are still on you to check.

Are .pt and .pth files safe to load?

They are pickles, the same as pytorch_model.bin, so the file format gives you no safety at all. Loading one runs the instructions inside it with your permissions. Safety comes entirely from the source: a .pt from a release you can tie to the lab that trained the model is a different risk from a .pt uploaded by an account you have never heard of. Scan it with Picklescan and read it with pickletools.dis before it reaches torch.load.

Is PyTorch's weights_only=True enough?

It is a real improvement, and it has been the torch.load default since 2.6.0, released 2025-01-29. It is not a wall. CVE-2025-32434, published 2025-04-17 with a CVSS 4.0 score of 9.3, showed code execution on versions below 2.6.0 even with the flag set, and CVE-2026-24747, published 2026-01-26 and rated High 8.8, affected everything below 2.10.0 through opcode and storage validation flaws. Keep PyTorch current and keep treating the file as untrusted.

Can GGUF files be malicious?

Not through the pickle mechanism. GGUF encodes tensors plus a standardized set of metadata, and it is not an opcode stream, so nothing in the file executes at load. Two things still deserve care: the chat template inside it is rendered by your runtime, so the file shapes behavior without running code, and most GGUF files are third-party quantized repacks rather than the publisher's own release. Prefer a repack tied to a named source and a published digest.

Does Hugging Face block malicious models?

It scans, flags, and advises. Every file is scanned at each commit for malware, pickle imports, and secrets, and a flagged file draws a warning plus advice to the repository owner to remove it, not a takedown. The docs say the scanning is not 100 percent foolproof. The rapid removals that did happen, nullifAI in under 24 hours, followed researcher reports rather than the scanners.

What was the nullifAI campaign?

A technique reported by ReversingLabs on 2025-01-20. Two models on the Hub carried pickles that were deliberately malformed, built as 7z containers where the scanner expected ZIP, so Picklescan's validation failed and the scan was skipped rather than the file flagged. The payload opened a reverse shell to a hardcoded address. Hugging Face removed both models in under 24 hours and Picklescan was patched to scan broken pickles.

Is scanning enough?

No, and the source for that is the scanners themselves. JFrog published three zero-day Picklescan bypasses on 2025-12-02, fixed in 0.0.31 on 2025-09-02: a CRC error in the container disables ZIP scanning, and the unsafe-import checks could be bypassed through subclasses. Scanning is a filter that raises the cost of an attack. Format choice, provenance, and a digest check are what cover the rest.

How do I check a model I already downloaded?

Compare the file against a digest from a source you trust, before you load it. For large weight files that is a sha256 against the published value; for the small git-stored files it is the git blob digest and size. If the digests travel in a signed list, check the signature first, because that is what makes the list worth comparing against. The command-by-command walkthrough is in the guide to verifying a model download.