{"course":{"slug":"hugging-face","title":"Hugging Face, End to End","url":"https://addaly.com/learn/hugging-face","level":"intermediate","track":"create"},"lesson":{"slug":"safetensors-and-pickle","title":"Loading a model can run someone else's code","url":"https://addaly.com/learn/hugging-face/safetensors-and-pickle","position":6,"of":75,"minutes":9,"answer":"A pickle checkpoint executes code when loaded, while safetensors is a JSON header plus raw bytes with no code path at all, but trust_remote_code=True reopens the hole by importing Python from the repository.","keyIdea":"A pickle checkpoint executes code when loaded, while safetensors is a JSON header plus raw bytes with no code path at all, but `trust_remote_code=True` reopens the hole by importing Python from the repository, so the weights format and the loading code are two separate decisions."},"body":{"format":"markdown","text":"## The thing nobody tells beginners\n\nA PyTorch `.bin` or `.pt` checkpoint is a Python **pickle**. Pickle is not a data format in the way JSON is a data format. It is a small instruction stream for rebuilding Python objects, and among its instructions is one that calls an arbitrary function. Unpickling therefore executes code by design.\n\nSo `torch.load(\"pytorch_model.bin\")` on a file from a stranger is, in the general case, running a program that stranger wrote, with your user's permissions, on your machine. Not a theoretical risk: malicious models have been found on the Hub, and the usual payload is boring and effective: read environment variables, find the tokens, send them somewhere.\n\nThis is the reason safetensors exists, and it is why the format is worth understanding rather than merely preferring.\n\n## What safetensors actually is\n\nA `.safetensors` file has two parts:\n\n1. A header: a JSON object listing every tensor, its dtype, its shape, and the byte offsets where its data begins and ends.\n2. The raw bytes of the tensors, one after another.\n\nThat is the whole specification. There is no instruction stream, no object graph, no code path that can be made to call a function. Loading it parses JSON and then points at memory.\n\nTwo consequences follow from that design, and both are practical rather than security-flavoured:\n\n- **It is faster.** Because tensors sit at known offsets, the loader can memory-map the file and hand the framework a view of it rather than copying. On large models this turns a minute into a few seconds, and it is why cold starts improved across the ecosystem when repositories converted.\n- **You can read one tensor without reading the file.** The header tells you offsets, so a tool can fetch a single layer over HTTP with a range request. That is how the Hub renders a tensor listing in the browser for a 70 GB model without downloading it.\n\n```python\nfrom safetensors.torch import load_file\nweights = load_file(\"model.safetensors\")   # no code executes\n```\n\n## Prefer safetensors, and say so in code\n\nTransformers picks safetensors automatically when both formats are present. When you want the guarantee rather than the default, ask for it:\n\n```python\nmodel = AutoModel.from_pretrained(\"some-org/some-model\", use_safetensors=True)\n```\n\nIf that raises, the repository has no safetensors copy, and you now have a decision to make rather than a silent risk. Many older repositories have one because a community contributor opened a conversion pull request. Check the Community tab before you conclude there is none.\n\n## The hole safetensors does not plug: `trust_remote_code`\n\nSome models use architectures that are not in the transformers library. Their repositories ship their own Python (`modeling_custom.py`, `configuration_custom.py`), and loading them requires:\n\n```python\nmodel = AutoModel.from_pretrained(\"some-org/new-architecture\", trust_remote_code=True)\n```\n\nThat flag means exactly what it says. It downloads Python from the repository and imports it. Safetensors weights do not protect you here at all, because the danger is no longer in the weights file.\n\nThe flag is not avoidable (new architectures need it for months before library support lands) so treat it as a decision with steps:\n\n- Read the `.py` files in Files and versions first. They are usually a few hundred lines of ordinary modelling code. Anything doing network calls, subprocess work or environment reads is a stop.\n- Pin the revision, so a later commit cannot change the code under you: `revision=\"<commit hash>\"` alongside `trust_remote_code=True`.\n- Prefer well-known organisations for this specifically, not because large labs are virtuous but because their repositories are watched by thousands of people.\n- Run it in a container or a throwaway Colab session when you can, with no tokens in the environment.\n\n## The Hub's own scanning, and its limits\n\nUploads are scanned: a pickle scanner that inspects the opcode stream for dangerous imports, plus third-party malware and secret scanning, and the results appear on the file listing. This is real and it catches real things.\n\nIt is also a heuristic on an adversarial problem. A scanner that lists suspicious imports can be evaded by an attacker who reaches the same call through an indirection the scanner does not model. Treat a clean scan as evidence, not as a guarantee, in the same way you treat an antivirus pass on a downloaded installer.\n\n## The five-second habit\n\nBefore loading anything from an account you do not recognise:\n\n1. Are there `.safetensors` files? Use them.\n2. Does loading need `trust_remote_code`? If yes, read the Python.\n3. Is there a pinned revision in your code? If no, add one.\n4. Are there tokens in this environment? If yes, and you are unsure, move to a session without them.\n\n## Do this now\n\nFind a repository that requires `trust_remote_code=True` and open its `modeling_*.py` in the browser. Read the imports at the top. That list (what the file is allowed to reach for) is the entire security review most of the time, and it takes about thirty seconds."},"check":{"question":"A team standardises on `use_safetensors=True` for every model they load, and also loads a new vision-language architecture with `trust_remote_code=True`. What risk remains?","options":[{"text":"None in practice: the Hub's pickle scanner covers repository Python files, so a clean scan plus safetensors closes both paths.","correct":false,"tempting":"Overstates the scanner, which targets pickle opcode streams and is heuristic rather than a guarantee for arbitrary Python."},{"text":"Safetensors files can still carry executable payloads in their JSON header metadata, which the loader parses before reading tensors.","correct":false,"tempting":"Misreads the format; the header is parsed as data, and there is no evaluation step for it to exploit."},{"text":"The remote code is imported and run regardless of weight format, with the user's full permissions.","correct":true},{"text":"Mixing the two flags forces transformers to fall back to the pickle loader, silently undoing the safetensors preference.","correct":false,"tempting":"Invents an interaction between two independent options."}]},"prev":{"slug":"inside-a-repository","title":"What the files in a model repository are for","url":"https://addaly.com/learn/hugging-face/inside-a-repository"},"next":{"slug":"four-shelves","title":"Four shelves, and picking the wrong one costs a weekend","url":"https://addaly.com/learn/hugging-face/four-shelves"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Loading a model can run someone else's code', in Hugging Face, End to End. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/hugging-face/safetensors-and-pickle (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Loading a model can run someone else’s code","courseTitle":"Hugging Face, End to End","module":{"index":1,"slug":"the-hub-as-a-repository-host","title":"The Hub, read properly"},"answer":null,"keyIdea":"A pickle checkpoint executes code when loaded, while safetensors is a JSON header plus raw bytes with no code path at all, but `trust_remote_code=True` reopens the hole by importing Python from the repository, so the weights format and the loading code are two separate decisions.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"the-thing-nobody-tells-beginners","runs":[{"text":"The thing nobody tells beginners"}]},{"t":"p","runs":[{"text":"A PyTorch "},{"text":".bin","code":true},{"text":" or "},{"text":".pt","code":true},{"text":" checkpoint is a Python "},{"text":"pickle","b":true},{"text":". Pickle is not a data format in the way JSON is a data format. It is a small instruction stream for rebuilding Python objects, and among its instructions is one that calls an arbitrary function. Unpickling therefore executes code by design."}]},{"t":"p","runs":[{"text":"So "},{"text":"torch.load(\"pytorch_model.bin\")","code":true},{"text":" on a file from a stranger is, in the general case, running a program that stranger wrote, with your user’s permissions, on your machine. Not a theoretical risk: malicious models have been found on the Hub, and the usual payload is boring and effective: read environment variables, find the tokens, send them somewhere."}]},{"t":"p","runs":[{"text":"This is the reason safetensors exists, and it is why the format is worth understanding rather than merely preferring."}]},{"t":"h2","id":"what-safetensors-actually-is","runs":[{"text":"What safetensors actually is"}]},{"t":"p","runs":[{"text":"A "},{"text":".safetensors","code":true},{"text":" file has two parts:"}]},{"t":"ol","start":1,"items":[[{"text":"A header: a JSON object listing every tensor, its dtype, its shape, and the byte offsets where its data begins and ends."}],[{"text":"The raw bytes of the tensors, one after another."}]]},{"t":"p","runs":[{"text":"That is the whole specification. There is no instruction stream, no object graph, no code path that can be made to call a function. Loading it parses JSON and then points at memory."}]},{"t":"p","runs":[{"text":"Two consequences follow from that design, and both are practical rather than security-flavoured:"}]},{"t":"ul","items":[[{"text":"It is faster.","b":true},{"text":" Because tensors sit at known offsets, the loader can memory-map the file and hand the framework a view of it rather than copying. On large models this turns a minute into a few seconds, and it is why cold starts improved across the ecosystem when repositories converted."}],[{"text":"You can read one tensor without reading the file.","b":true},{"text":" The header tells you offsets, so a tool can fetch a single layer over HTTP with a range request. That is how the Hub renders a tensor listing in the browser for a 70 GB model without downloading it."}]]},{"t":"code","lang":"python","label":"Python","text":"from safetensors.torch import load_file\nweights = load_file(\"model.safetensors\")   # no code executes"},{"t":"h2","id":"prefer-safetensors-and-say-so-in-code","runs":[{"text":"Prefer safetensors, and say so in code"}]},{"t":"p","runs":[{"text":"Transformers picks safetensors automatically when both formats are present. When you want the guarantee rather than the default, ask for it:"}]},{"t":"code","lang":"python","label":"Python","text":"model = AutoModel.from_pretrained(\"some-org/some-model\", use_safetensors=True)"},{"t":"p","runs":[{"text":"If that raises, the repository has no safetensors copy, and you now have a decision to make rather than a silent risk. Many older repositories have one because a community contributor opened a conversion pull request. Check the Community tab before you conclude there is none."}]},{"t":"h2","id":"the-hole-safetensors-does-not-plug-trustremotecode","runs":[{"text":"The hole safetensors does not plug: "},{"text":"trust_remote_code","code":true}]},{"t":"p","runs":[{"text":"Some models use architectures that are not in the transformers library. Their repositories ship their own Python ("},{"text":"modeling_custom.py","code":true},{"text":", "},{"text":"configuration_custom.py","code":true},{"text":"), and loading them requires:"}]},{"t":"code","lang":"python","label":"Python","text":"model = AutoModel.from_pretrained(\"some-org/new-architecture\", trust_remote_code=True)"},{"t":"p","runs":[{"text":"That flag means exactly what it says. It downloads Python from the repository and imports it. Safetensors weights do not protect you here at all, because the danger is no longer in the weights file."}]},{"t":"p","runs":[{"text":"The flag is not avoidable (new architectures need it for months before library support lands) so treat it as a decision with steps:"}]},{"t":"ul","items":[[{"text":"Read the "},{"text":".py","code":true},{"text":" files in Files and versions first. They are usually a few hundred lines of ordinary modelling code. Anything doing network calls, subprocess work or environment reads is a stop."}],[{"text":"Pin the revision, so a later commit cannot change the code under you: "},{"text":"revision=\"<commit hash>\"","code":true},{"text":" alongside "},{"text":"trust_remote_code=True","code":true},{"text":"."}],[{"text":"Prefer well-known organisations for this specifically, not because large labs are virtuous but because their repositories are watched by thousands of people."}],[{"text":"Run it in a container or a throwaway Colab session when you can, with no tokens in the environment."}]]},{"t":"h2","id":"the-hub-s-own-scanning-and-its-limits","runs":[{"text":"The Hub’s own scanning, and its limits"}]},{"t":"p","runs":[{"text":"Uploads are scanned: a pickle scanner that inspects the opcode stream for dangerous imports, plus third-party malware and secret scanning, and the results appear on the file listing. This is real and it catches real things."}]},{"t":"p","runs":[{"text":"It is also a heuristic on an adversarial problem. A scanner that lists suspicious imports can be evaded by an attacker who reaches the same call through an indirection the scanner does not model. Treat a clean scan as evidence, not as a guarantee, in the same way you treat an antivirus pass on a downloaded installer."}]},{"t":"h2","id":"the-five-second-habit","runs":[{"text":"The five-second habit"}]},{"t":"p","runs":[{"text":"Before loading anything from an account you do not recognise:"}]},{"t":"ol","start":1,"items":[[{"text":"Are there "},{"text":".safetensors","code":true},{"text":" files? Use them."}],[{"text":"Does loading need "},{"text":"trust_remote_code","code":true},{"text":"? If yes, read the Python."}],[{"text":"Is there a pinned revision in your code? If no, add one."}],[{"text":"Are there tokens in this environment? If yes, and you are unsure, move to a session without them."}]]},{"t":"h2","id":"do-this-now","runs":[{"text":"Do this now"}],"role":"try"},{"t":"p","runs":[{"text":"Find a repository that requires "},{"text":"trust_remote_code=True","code":true},{"text":" and open its "},{"text":"modeling_*.py","code":true},{"text":" in the browser. Read the imports at the top. That list (what the file is allowed to reach for) is the entire security review most of the time, and it takes about thirty seconds."}]}],"check":{"question":[{"text":"A team standardises on "},{"text":"use_safetensors=True","code":true},{"text":" for every model they load, and also loads a new vision-language architecture with "},{"text":"trust_remote_code=True","code":true},{"text":". What risk remains?"}],"options":[{"text":[{"text":"None in practice: the Hub’s pickle scanner covers repository Python files, so a clean scan plus safetensors closes both paths."}],"correct":false,"tempting":[{"text":"Overstates the scanner, which targets pickle opcode streams and is heuristic rather than a guarantee for arbitrary Python."}]},{"text":[{"text":"Safetensors files can still carry executable payloads in their JSON header metadata, which the loader parses before reading tensors."}],"correct":false,"tempting":[{"text":"Misreads the format; the header is parsed as data, and there is no evaluation step for it to exploit."}]},{"text":[{"text":"The remote code is imported and run regardless of weight format, with the user’s full permissions."}],"correct":true,"tempting":null},{"text":[{"text":"Mixing the two flags forces transformers to fall back to the pickle loader, silently undoing the safetensors preference."}],"correct":false,"tempting":[{"text":"Invents an interaction between two independent options."}]}],"order":[0,1,3,2],"why":null},"prev":{"slug":"inside-a-repository","title":"What the files in a model repository are for","minutes":10},"next":{"slug":"four-shelves","title":"Four shelves, and picking the wrong one costs a weekend","minutes":8}}}