YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
ModelScan 0.8.x ACE bypass via timeit.timeit โ unblocked stdlib module
Proof of concept for the Protect AI / huntr.com Model Format Vulnerability program.
Vulnerability
ModelScan 0.8.8 maintains an unsafe_globals blocklist to detect dangerous pickle payloads. The timeit module is completely absent from this blocklist at every severity level (CRITICAL, HIGH, MEDIUM, LOW).
timeit.timeit(stmt, setup) compiles and executes the setup argument exactly once as Python code via compile() and exec() internally, then runs stmt N times for timing purposes. Placing a malicious command in setup achieves single-execution arbitrary code execution while stmt='pass' runs harmlessly in the background.
The payload contains a single GLOBAL reference: ('timeit', 'timeit'). This is not present in unsafe_globals. ModelScan reports clean.
Affected
- Scanner: ModelScan 0.8.8 (latest as of July 2026)
- Extensions: .pkl .pickle .joblib .dill .dat .data
- Trigger: pickle.loads() and joblib.load()
Reproduction
Install requirements:
pip install modelscan joblib
Generate the PoC files:
python3 poc_timeit_ace.py
This creates poc_timeit_bypass.pkl and poc_timeit_bypass.joblib and confirms that os.system() executes at deserialization time.
Scan with ModelScan:
modelscan -p poc_timeit_bypass.pkl
Expected output: No issues found
The indicator file /tmp/ml_pwned_timeit was already created by the PoC script, demonstrating that execution happened before the scan result was even printed.
Payload
Pickle protocol 4 bytecode:
SHORT_BINUNICODE 'timeit' MEMOIZE(0)
SHORT_BINUNICODE 'timeit' MEMOIZE(1)
STACK_GLOBAL MEMOIZE(2) <- timeit.timeit function
SHORT_BINUNICODE 'pass' MEMOIZE(3) <- stmt: harmless, runs 1M times
SHORT_BINUNICODE 'import os...' MEMOIZE(4) <- setup: executes once -> RCE
TUPLE2 MEMOIZE(5)
REDUCE <- timeit.timeit('pass', payload)
STOP
Total payload size: 93 bytes.
Root Cause
unsafe_globals in modelscan/settings.py does not include the timeit module. The blocklist was built around well-known dangerous modules (os, subprocess, sys, socket, etc.) but does not account for stdlib modules that indirectly execute code strings via compile()+exec(), such as timeit, doctest, and trace.
Fix
Add to unsafe_globals CRITICAL:
"timeit": "*",
Files
- poc_timeit_ace.py: generates and verifies both PoC files
- poc_timeit_bypass.pkl: malicious pickle, passes ModelScan, executes os.system()
- poc_timeit_bypass.joblib: same payload with .joblib extension
Ethics
Payload uses a benign indicator (id > /tmp/ml_pwned_timeit). Submitted exclusively through the Protect AI / huntr.com bug bounty program.