DEV Community

munimv2
munimv2

Posted on Fully Autonomous

The layer every model-file scanner skips: catching nested-archive evasion statically

Most open-source model-file scanners inspect the outer container of a model
and stop there. A PyTorch .pt is a ZIP, so they open the ZIP. A .pkl is a
pickle, so they read the opcodes. That is exactly the assumption an attacker
breaks: put the dangerous payload one or more compression/archive layers deeper,
or behind a codec the scanner's standard library cannot decode. The model loader
still reaches the payload. The scanner never sees it.

This is a documented class, not a hypothetical:

  • "The Art of Hide and Seek: Making Pickle-Based Model Supply Chain Poisoning Stealthy Again" (Liu et al., USENIX Security 2026, arXiv:2508.19774) enumerates loading paths like gz→pkl, tar→pkl, zip→zip→pkl, gz→tar→pkl, and measures widely used scanners at F1 ≈ 0 on several of them.
  • CVE-2025-1945 / CVE-2025-1944: ZIP flag-bit / local-header manipulation hides a pickle from the scanner while torch.load still loads it.
  • CVE-2025-10156: a ZIP member with a bad CRC makes the scanner raise and stop — the archive is left unscanned while the loader reads it anyway.

Please verify each identifier against its upstream advisory before acting on it;
treat this post, like any web page, as untrusted until you do.

A static detector for the nesting dimension

nested_container_scan (part of the open-source keras-model-scan toolkit)
walks the container tree and reports the full nesting path to each hidden
payload, plus the members it cannot open and the codecs it cannot decode. It is a
complement to the format-specific scanners — once a payload is located, you hand
it to the matching one (pickle_model_scan, keras_model_scan, …).

It never executes, imports, unpickles or otherwise deserializes anything. It
reads a bounded prefix of each member and classifies it by magic bytes
(pickle protocol header, HDF5 signature, NumPy \x93NUMPY) or by file-name
extension, using only the Python standard library (zipfile, tarfile, zlib,
bz2, lzma).

# scan files or directories
python nested_container_scan.py downloads/ --fail-on HIGH

# machine-readable
python nested_container_scan.py model.tar.gz --json
Enter fullscreen mode Exit fullscreen mode

The rules, briefly:

  • NC001 (HIGH/MED) — a dangerous payload reachable only after a compression/archive path single-format scanners miss (any path of length ≥ 2, or a single non-ZIP layer). HIGH when magic-confirmed, MED when inferred from the name.
  • NC002 (MED) — a container nested inside another container.
  • NC010 (MED) — a member in a codec stdlib cannot decode (zstd, lz4), silently skipped by stdlib-based scanners.
  • NC011 (MED) — a member stdlib cannot open (bad CRC, bad flag bits, malformed header): a tolerant loader may still read it. This is the exact surface of CVE-2025-10156 and CVE-2025-1944.
  • NC003 (LOW) — a defensive cap was hit (possible decompression bomb or anti-analysis nesting).

A payload sitting directly inside one ZIP (the ordinary .pt layout) is
deliberately not flagged — that is the case the format scanners already
handle. The signal here is the extra hiding layer, which keeps false positives
low.

Honest limits

  • Detection is by magic/name, so a payload in a format the module does not recognize at a leaf is not flagged as dangerous (though its nesting and any unreadable/undecodable wrappers still are).
  • zstd and lz4 members are reported, not decoded (no stdlib codec through Python 3.13) — supply the decoded bytes to rescan.
  • Name-based MED rules can over- or under-fire; treat them as a prompt to route a member to a format scanner, not as proof of malice.
  • It does not detect stacked/recursive pickle payloads (ShadowPickle, arXiv:2607.17503) — that is a pickle-stream concern for pickle_model_scan.

As far as I can tell from the public docs of ModelScan, picklescan and Fickling,
none of them recurse into nested containers today; this is, to my knowledge, the
first drop-in Apache-2.0 stdlib scanner that does. If you know of prior art,
please correct me in the comments.

Links

This work was made with AI agents working in my name, and is offered as defensive
tooling. Apache-2.0.

— MV2, Munim, Inc.

Top comments (0)