vLLM Malicious Model Checkpoint Deserialization
vLLM and similar tools load model weights using Python's pickle format, which can run arbitrary code during loading. An attacker who can get a poisoned checkpoint file loaded gets code execution on the machine that loads it.
How the attack works
The attack starts with a poisoned model checkpoint file crafted using Python's pickle __reduce__ mechanism combined with torch.save, so that unpickling the file runs attacker-chosen code instead of just restoring model weights. The victim downloads or is pointed at this checkpoint - often disguised as a normal HuggingFace or PyTorch weights file (pytorch_model.bin, .pt, .pth, .ckpt) - and loads it with torch.load or a vLLM entrypoint using a pickle-based load format, sometimes with weights_only explicitly disabled or with --trust-remote-code set. The moment the file is unpickled, the embedded code executes with the privileges of the loading process. Variants include feeding the unpickler an inline base64/hex-encoded payload instead of a file, which skips any file-based scanning.
Netzilo detection
Netzilo reports this behaviour when it is observed.
- Signature ID
- 7c1f4b2e-9a63-4d81-b0c5-3f6a2d5e8471
- Severity
- High
Why it matters
Successful exploitation gives an attacker arbitrary code execution on whatever host loads the model, which can lead to full compromise of the ML serving machine, theft of other models or data on that host, and a foothold for further lateral movement.
What you can do
- →Load model weights only in the safetensors format, which does not support arbitrary code execution during load, and avoid torch.load on files from untrusted sources.
- →Never set weights_only=False unless you fully trust the checkpoint's origin and integrity.
- →Avoid --trust-remote-code and pickle-based --load-format options in vLLM unless the model source is verified and controlled by you.
- →Review any process command line that unpickles a checkpoint fetched from an external URL or from an inline base64/hex blob, and treat it as a candidate compromise until confirmed as legitimate conversion work.
Known benign look-alikes
- ML engineers deliberately loading a legacy full-object PyTorch checkpoint that cannot be read with weights_only=True (fine-tune conversion, old research checkpoints) - this is the same primitive as the attack and is intentionally surfaced for review.
- Model conversion / quantization pipelines (AWQ, GPTQ, GGUF, safetensors converters) that unpickle pytorch_model*.bin out of the local HuggingFace cache.
- vLLM deployments that legitimately require --trust-remote-code for a community architecture and are started with --load-format pt because no safetensors weights are published.
- CI jobs and unit tests exercising checkpoint loaders - suppressed by filter_test_runner.
- Documentation, remediation, linting or security-scanning commands that only contain the literal strings - suppressed by filter_read_only_tools, filter_string_printing and filter_security_research.