modelaudit joblib lz4 decompression gap (PoC)
Security-research artifact. The payload is a benign marker. It prints the
string MODEL_LOAD_RCE_POC and writes the output of id to
/tmp/modelaudit_lz4_poc.txt. It is not malware. It exists only to demonstrate a
static-scanner coverage gap for authorized model-file-vulnerability (MFV)
research and coordinated disclosure.
What this shows
joblib supports lz4 as a first-class compression backend whenever the lz4
package is installed. A model saved with
joblib.dump(obj, "model.joblib", compress="lz4") runs its embedded pickle on a
normal joblib.load("model.joblib").
modelaudit's joblib scanner decompresses joblib streams (zlib, gzip, bz2, lzma,
xz) and flags dangerous pickle opcodes, but it does not decompress lz4. So an
lz4-compressed joblib reaches joblib.load and executes, while modelaudit
produces no security finding: risk_score 0.0, zero failed checks, zero
Critical or Warning issues. The identical payload under any other codec is
flagged CRITICAL, which proves this is a detector bypass and not a missing
feature.
Files
model.joblib- crafted lz4-compressed joblib (frame magic04224d18). sha2563c652b8a2497890c2a507a6272e276ad75830eea013465b21236e8c4a9d314f8. Evades modelaudit.positive_control_zlib.joblib- same payload, zlib codec. sha256ce464434b3e4ea773bcfeafad896eacd8eb2618a0b4d5ec061573c90fcd453da. modelaudit detects this one (CRITICAL posix.system).build_evil_joblib_lz4.py- regenerates the artifacts.
Pinned versions
modelaudit 0.2.51 (vendored modelaudit-picklescan 0.1.9), joblib 1.5.3, lz4 4.4.5, Python 3.11.
Reproduce
pip install modelaudit==0.2.51 joblib==1.5.3 lz4==4.4.5
python -c "import joblib; joblib.load('model.joblib')" # marker fires, RCE primitive executes
modelaudit scan model.joblib # no security finding, risk 0.0, exit 2
modelaudit scan positive_control_zlib.joblib # CRITICAL posix.system, exit 1
Result matrix (same os.system payload, 7 joblib codecs)
codec picklescan modelscan modelaudit joblib.load
raw exit1 CRITICAL exit1 issues=1 exit1 CRITICAL posix.system RCE fires
zlib exit0 0 dangerous exit3 issues=0 exit1 CRITICAL posix.system RCE fires
gzip exit0 0 dangerous exit3 issues=0 exit1 CRITICAL posix.system RCE fires
bz2 exit0 0 dangerous exit3 issues=0 exit1 CRITICAL posix.system RCE fires
lzma exit0 0 dangerous exit3 issues=0 exit1 CRITICAL posix.system RCE fires
xz exit0 0 dangerous exit3 issues=0 exit1 CRITICAL posix.system RCE fires
lz4 exit0 0 dangerous exit3 issues=0 exit2 INFO only, risk 0.0 RCE fires <== BYPASS
picklescan and modelscan never decompress joblib at all, so they miss every compressed variant (already public prior art). modelaudit decompresses six of the seven codecs and flags the embedded pickle, but cannot decompress lz4, so it emits no security finding for the lz4 file. That modelaudit lz4 gap is the point of this PoC.
Root cause
modelaudit joblib_scanner.py: the _safe_decompress codec list is
{zlib, gzip, bz2, lzma} and _JOBLIB_COMPRESSED_PREFIXES omits the lz4 frame
magic 04224d18. An lz4 joblib matches neither the compressed-prefix heuristic
nor the raw-pickle heuristic, so decompression fails, the scanner records an INFO
"unable to decompress" note (rule S902), sets an operational error, and returns
without scanning any pickle bytes. The dangerous posix.system REDUCE is never
analyzed.
A note on the exit code
On the lz4 file modelaudit exits 2 and marks the scan an operational error,
rather than exiting 0. A consumer that blocks on any non-zero exit is fail-closed
and is not fooled. However the security signal modelaudit actually emits says the
file is clean: risk_score 0.0, zero failed checks, zero Critical or Warning
findings, and a "100% success rate, no critical issues" summary, with only an
INFO note about decompression. Consumers that gate on the security verdict, which
is the intended use of a model scanner, are bypassed, and the dangerous REDUCE is
never surfaced.
Suggested fix
- Add an lz4 frame decoder to the joblib scanner's decompressor, ideally driving the codec list from joblib's own registered compressors.
- Treat a joblib payload that cannot be decompressed but is still loadable as a fail-closed WARNING or higher, not an INFO note.