What: Two critical vulnerabilities in PickleScan, a Python serialised-object security scanner used in ML pipelines and model-sharing platforms including HuggingFace Hub, render its blocklist-based protection entirely ineffective. The first advisory (GHSA-g38g-8gr9-h9xp, CVSS 9.8 ) identifies six Python standard library modules containing direct remote code execution paths that are absent from PickleScan's blocklist. The second advisory (GHSA-vvpj-8cmc-gx39, CVSS 10.0) describes a universal blocklist bypass via pkgutil.resolve_name that allows an attacker to dynamically resolve and invoke any callable in the Python runtime, regardless of blocklist contents.
So What: PickleScan is a commonly used scanner for evaluating serialised object safety before model loading, notably deployed by HuggingFace Hub for server-side scanning of uploaded models (GHSA-g38g-8gr9-h9xp). Organisations that rely on PickleScan as their sole defence against malicious serialised models have been operating with a false sense of security. Any model artefact scanned and marked "safe" by versions prior to 1.0.4 should be re-evaluated and not relied upon as solely validated, as an attacker could have embedded payloads using any of the unblocked modules or the universal bypass technique.
Now What: Upgrade PickleScan to version 1.0.4 or later immediately. Audit all models previously scanned and cleared by earlier versions. Consider defence-in-depth strategies that do not rely solely on blocklist-based scanning of serialised objects.
If any of the above apply, you are affected and should upgrade immediately.
Check your installed version:
PickleScan is a security scanner designed to detect dangerous function calls within Python serialised object files before they are deserialised. It operates on a blocklist model: it maintains a list of known-dangerous modules and callables (such as os.system , subprocess.Popen , and builtins.exec ) and flags files that reference them. This research documents two distinct classes of failure in this approach, disclosed via GitHub Security Advisories.
The first class (GHSA-g38g-8gr9-h9xp) identifies six Python standard library modules that provide direct paths to operating system command execution but were absent from PickleScan's blocklist. These modules reach os.system() or subprocess.Popen through internal call chains that are not immediately obvious from their public API surface.
The second class (GHSA-vvpj-8cmc-gx39) is architecturally more severe: pkgutil.resolve_name can dynamically resolve any dotted Python name to its corresponding callable at runtime. Because pkgutil was not blocklisted, an attacker can use the serialisation opcode sequence STACK_GLOBAL + REDUCE to first resolve an arbitrary dangerous function (e.g., os:system ) and then invoke it with attacker-controlled arguments -- bypassing the entire blocklist regardless of its contents. The advisory confirms eleven-plus RCE chains exploitable through this single bypass.
Together, these advisories demonstrate that blocklist-based scanning of serialised objects is structurally fragile: a single gap in coverage -- whether an omitted module or an unblocked meta-resolution function -- can negate the entire protection model.
Six unblocked stdlib RCE modules. PickleScan versions prior to 1.0.4 did not blocklist six Python standard library modules that provide direct command execution capabilities, including uuid._get_command_stdout (which calls subprocess.Popen ), _osx_support._read_output (which calls os.system() ), and imaplib.IMAP4_stream (which calls subprocess.Popen(shell=True) ) (GHSA-g38g-8gr9-h9xp).
Universal blocklist bypass via pkgutil.resolve_name. The pkgutil.resolve_name function accepts a dotted-name string (e.g., os:system ) and returns the corresponding Python callable. Because pkgutil was not blocklisted, an attacker can use serialisation opcodes to call resolve_name first to obtain any dangerous function, then invoke it in a second REDUCE operation -- entirely circumventing the blocklist architecture (GHSA-vvpj-8cmc-gx39).
Eleven-plus confirmed RCE chains. The pkgutil.resolve_name bypass is not limited to a single dangerous callable. The advisory confirms that eleven or more distinct remote code execution chains are reachable through this single bypass vector, making it a universal escape from blocklist-based scanning (GHSA-vvpj-8cmc-gx39).
Blocklist architecture is structurally incomplete (assessment). The Python standard library contains hundreds of modules, many of which have internal functions that reach command execution through indirect call paths. A blocklist approach requires exhaustive enumeration of every such path -- a task that these two advisories demonstrate is inherently fragile and prone to omission.
False-negative scanning results degrade trust. Models previously scanned and cleared by vulnerable PickleScan versions should not be relied upon as solely validated. Any organisation that used scan-pass results as a security gate should re-evaluate the integrity of all models that passed through that gate.
MITRE ATLAS mapping: AML.T0010 (ML Supply Chain Compromise) -- attacker poisons a model artefact in the supply chain; AML.T0010.001 (AI Software) -- the compromised component is the security scanning tool itself.
PickleScan's blocklist architecture relies on matching the module and callable names referenced in serialisation opcodes against a curated deny list. The following six standard library modules were absent from this list, each providing a path to operating system command execution (GHSA-g38g-8gr9-h9xp):
uuid._get_command_stdout -- This internal function in the uuid module calls subprocess.Popen to execute shell commands as part of UUID generation on certain platforms. A serialised file can reference uuid._get_command_stdout directly via STACK_GLOBAL and REDUCE opcodes, passing attacker-controlled command strings.
_osx_support._read_output -- Used internally by Python's build system on macOS, this function calls os.system() to execute arbitrary commands. Despite being prefixed with an underscore (indicating internal use), it is fully importable and reachable via deserialisation.
_aix_support._read_cmd_output -- The AIX platform support module mirrors the macOS pattern, calling os.system() to read command output. Like _osx_support , it is importable and was not blocklisted.
_pyrepl.pager.pipe_pager -- Part of Python's REPL infrastructure, this function calls subprocess.Popen(shell=True) to pipe content through a system pager. The shell=True parameter makes it particularly dangerous, as it allows shell metacharacter injection.
imaplib.IMAP4_stream -- The IMAP client library's stream class calls subprocess.Popen(shell=True) to establish connections. An attacker can abuse this to execute arbitrary commands via the connection command string.
test.support.script_helper.assert_python_ok -- Part of Python's test infrastructure, this function spawns Python subprocesses. While typically excluded from production distributions, it remains importable in standard CPython installations.
The pkgutil.resolve_name bypass is architecturally distinct from the unblocked-module issue. Rather than exploiting gaps in the blocklist, it renders the entire blocklist concept irrelevant (GHSA-vvpj-8cmc-gx39).
The attack operates through a two-stage serialisation opcode sequence:
Stage 1 -- Resolve the dangerous callable:
At this point, the virtual machine stack contains the os.system function object. PickleScan only inspects the STACK_GLOBAL and REDUCE opcodes for blocklisted module/callable pairs. It sees pkgutil.resolve_name -- which is not blocklisted -- and allows the operation.
Stage 2 -- Invoke the resolved callable:
The second REDUCE calls whatever function was returned by Stage 1, with attacker-controlled arguments. The blocklist never sees os.system referenced in an opcode -- it was resolved dynamically at runtime.
The advisories map to two complementary CWE weaknesses:
In our assessment, the fundamental weakness is the use of a deny-list (blocklist) rather than an allow-list for a security-critical function. The Python standard library contains hundreds of modules, and any module with even an indirect path to command execution, file system manipulation, or network access can be weaponised in a serialised payload. Maintaining an exhaustive deny-list requires continuous updates, as each new Python release may introduce new modules or internal functions with execution capabilities.
The pkgutil.resolve_name bypass elevates this from a coverage problem to an architectural one: even a theoretically complete blocklist of dangerous endpoints is insufficient if the blocklist does not also cover every possible meta-programming function that can resolve names dynamically.
Assessment confidence: HIGH. Both advisories are published by the package maintainer with detailed technical descriptions. The vulnerable code paths are verifiable through source code inspection. The fix version is published and installable.
Upgrade PickleScan. Install version 1.0.4 or later from PyPI:
Verify the installed version:
Audit previously scanned models. Any model artefact that was scanned and cleared by PickleScan versions prior to 1.0.4 should be re-evaluated, as prior scan results cannot be relied upon as sole validation. Re-scan all such artefacts with the updated version. Pay particular attention to models sourced from public repositories, community contributions, or third-party vendors.
Review CI/CD pipelines. Identify all pipelines that invoke PickleScan and ensure they reference the fixed version. Update version pins in requirements.txt , pyproject.toml , and Docker images.
Do not rely solely on blocklist-based scanning. Blocklist approaches are structurally incomplete for serialised object analysis. Consider supplementary controls:
These PickleScan advisories illustrate a broader pattern in ML supply chain security: the tools designed to protect the pipeline are themselves operating on architecturally fragile assumptions. In our assessment, blocklist-based scanning follows a well-understood anti-pattern in traditional application security -- web application firewalls (WAFs) that rely solely on regex-based blocklists have been routinely bypassed for decades. The ML security ecosystem is now encountering the same structural limitation.
Python's serialisation format remains prevalent in ML workflows (RAXE assessment), including as the default format for PyTorch model checkpoints. The deserialisation call is fundamentally an exec equivalent -- it can instantiate arbitrary objects and invoke arbitrary callables. As these advisories demonstrate, pre-load scanning alone cannot fully eliminate this risk as long as the format permits arbitrary code execution by design.
Formats such as SafeTensors (developed by Hugging Face) and ONNX store tensor data without executable code paths, eliminating the deserialisation attack surface entirely. These PickleScan advisories reinforce the value of non-executable formats as a complementary control: where serialised object scanning is structurally fragile, format-level elimination of code execution removes the attack class altogether. The extent of industry migration to these formats is beyond the scope of this report.
Any organisation that treats a single blocklist-based scanner as a sufficient security control for model ingestion risks operating with a single point of failure. These advisories reinforce the need for layered model validation: format verification, allowlist-based opcode inspection, sandboxed loading, and provenance verification should all contribute to model trust decisions.
The CWE classifications assigned to these advisories -- CWE-184 (Incomplete List of Disallowed Inputs) and CWE-183 (Permissive List of Allowed Inputs) -- directly map to the allow-vs-deny list architectural debate. Security practitioners should treat these advisories as a case study in why deny-list approaches require continuous maintenance and are structurally inferior to allow-list models for security-critical functions.
GHSA-g38g-8gr9-h9xp -- PickleScan: 7 Python stdlib modules with direct RCE not in blocklist (CVSS 9.8).
GHSA-vvpj-8cmc-gx39 -- PickleScan: pkgutil.resolve_name universal blocklist bypass with eleven-plus confirmed RCE chains (CVSS 10.0).
MITRE ATLAS AML.T0010 -- ML Supply Chain Compromise.
MITRE ATLAS AML.T0010.001 -- ML Supply Chain Compromise: AI Software.
CWE-184 -- Incomplete List of Disallowed Inputs.
CWE-183 -- Permissive List of Allowed Inputs.
CWE-693 -- Protection Mechanism Failure.
PickleScan on PyPI.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
