ModelAudit CompressedScanner codec-list gap: zstd and brotli compressed pickles read as Clean
Security-research artifact. Every payload here is BENIGN. The pickles run a harmless marker command only: they print the string
MODEL_LOAD_RCE_POCand write the output ofidto a temp file. There is no malware, no network callback, and no persistence. These files exist to demonstrate a static-scanner detection gap for authorized, responsible disclosure.
What this shows
ModelAudit ships a CompressedScanner that decompresses standalone compression
wrappers and routes the inner payload to its pickle scanner, catching malicious
pickles hidden under compression. Its codec set covers gzip, bz2, xz, lz4, and
zlib. It does not cover Zstandard or Brotli.
A pickle compressed with zstd (model.pkl.zst) or brotli (model.pkl.br) is
handled by no ModelAudit scanner and reported completely clean: risk score 0.0,
zero checks, empty issue list, success: true, exit code 0, human summary
"NO ISSUES FOUND". The byte-for-byte identical pickle under gzip, bz2, or xz is
flagged CRITICAL ("Found REDUCE opcode invoking dangerous global: posix.system").
Same payload, one codec the scanner omits, and the detector goes silent. That is
the two-sided proof of a detector gap, not a missing feature.
The zstd case is a single-call loader gap: pandas.read_pickle("model.pkl.zst")
infers the zstd codec from the extension, decompresses, and unpickles it in one
standard documented API call, so the marker fires. The brotli case uses the
common two-step pickle.loads(brotli.decompress(...)) idiom; ModelAudit still
cannot see it, because a brotli frame has no magic signature bytes and .br is
not in the codec list, so it cannot be identified by extension or by content.
Root cause
modelaudit/scanners/compressed_scanner.py:
supported_extensions = [".gz", ".bz2", ".xz", ".lz4", ".zlib"]
_EXTENSION_TO_CODEC = {".gz": "gzip", ".bz2": "bzip2", ".xz": "xz",
".lz4": "lz4", ".zlib": "zlib"}
_CODEC_MAGIC_PREFIXES = {"gzip": b"\x1f\x8b", "bzip2": b"BZh",
"xz": b"\xfd7zXZ\x00", "lz4": b"\x04\x22\x4d\x18"}
.zst / .zstd and .br are absent from the extension map; the zstd frame
magic 28 b5 2f fd is absent from the magic map (and brotli has no magic at
all), so can_handle() returns False and the file is recorded as
type: "unknown" with no checks run.
Files
| File | Codec | ModelAudit verdict | Loader result |
|---|---|---|---|
model.pkl.zst |
zstd | CLEAN, risk 0.0, exit 0 (BYPASS) | pandas.read_pickle runs it |
model.pkl.br |
brotli | CLEAN, risk 0.0, exit 0 (BYPASS) | brotli.decompress + pickle.loads runs it |
control.pkl.gz |
gzip | CRITICAL posix.system, exit 1 | pandas.read_pickle runs it |
control.pkl.bz2 |
bz2 | CRITICAL posix.system, exit 1 | pandas.read_pickle runs it |
control.pkl.xz |
xz | CRITICAL posix.system, exit 1 | pandas.read_pickle runs it |
build_evil_zst.py |
builder | regenerates the zstd file plus gz/bz2/xz controls | |
build_evil_brotli.py |
builder | regenerates the brotli file plus the gz control |
Every .pkl.* file wraps the same inner pickle: a single REDUCE over
os.system with the benign marker command.
Reproduce
Pinned versions used in the lab: modelaudit 0.2.51 (vendored engine modelaudit-picklescan 0.1.9), pandas 3.0.3, zstandard 0.25.0, brotli 1.2.0, CPython 3.11.
pip install modelaudit==0.2.51 pandas==3.0.3 zstandard==0.25.0 brotli==1.2.0
# zstd: single standard call executes the marker
python -c "import pandas; pandas.read_pickle('model.pkl.zst')" # prints MODEL_LOAD_RCE_POC
# brotli: two-step decompress + unpickle executes the marker
python -c "import brotli,pickle; pickle.loads(brotli.decompress(open('model.pkl.br','rb').read()))"
# ModelAudit misses both (Clean, risk 0.0, exit 0)
modelaudit scan model.pkl.zst
modelaudit scan model.pkl.br
# same pickle under gzip is caught (CRITICAL posix.system, exit 1)
modelaudit scan control.pkl.gz
Suggested fix
- Add zstd (extension
.zst/.zstd, magic28 b5 2f fd) and brotli (extension.br) decoders to the CompressedScanner, driving the codec list from a single registry so every codec a common loader supports is covered everywhere. - Treat any file that no scanner can analyze, but that decompresses to a pickle or model, as fail-closed (WARNING or higher, non-zero exit) rather than a silent clean pass.
Scope and safety
Reported for responsible disclosure against ModelAudit. The command string in every pickle is a harmless marker. No production system was involved; all execution was performed inside a throwaway container.