What it detects
Scans pickle-based model files for code-execution risk without loading them.
Loading a pickle (torch.load, joblib.load, pickle.load, np.load(allow_pickle=True)) can run any Python the file asks for. pickleprobe walks the pickle opcodes with the standard library's pickletools (it never unpickles) and reports which Python callables a load would import, so you can check a model downloaded from a hub or handed over by a teammate before anything touches it.
Supported inputs: raw pickles (.pkl, .pickle, .joblib, .bin, .dat, ...), PyTorch zip checkpoints (.pt, .pth, .ckpt, .bin), NumPy .npy / .npz, and Keras .keras archives. Files are recognised by their bytes, not only their extension, so a pickle renamed to model.safetensors is caught.
Rules
| Rule | Severity | Finding |
|---|---|---|
| PP01 | critical | Dangerous import: os/posix/nt, subprocess, builtins.eval/exec/getattr/open/__import__, socket, runpy, importlib, ctypes, marshal, network clients, operator.attrgetter, functools.partial, types.CodeType, nested pickle/torch.load and more |
| PP02 | medium | Import that is not on the safe allowlist (tensor rebuilders, OrderedDict, NumPy reconstructors, plain builtins). Review it, or allow it with --allow |
| PP03 | medium | Pickle stream, .npy header, zip or Keras config could not be fully parsed. Scanners that give up early are a known bypass, so this is not ignored |
| PP04 | high | Pickle data in a file named like a safe format (.safetensors, .json, .png, .onnx, .gguf, ...); medium for a zip with such a name |
| PP05 | medium | NumPy object array, which needs allow_pickle=True and unpickles its payload (the payload is scanned too) |
| PP06 | high | Archive member with an absolute path or .. |
| PP07 | high | Keras Lambda layer (serialized Python code); medium for other serialized functions |
| PP08 | info | The file uses a pickle-based format at all. Prefer safetensors |
Usage
cd tools/pickleprobe
python3 examples/make_examples.py # writes FAKE examples/risky and examples/clean
python3 pickleprobe.py examples/risky # 17 findings, exit 1
python3 pickleprobe.py examples/clean # info only, exit 0
python3 pickleprobe.py ~/models --json --min-severity medium
python3 pickleprobe.py model.pt --allow 'mylib.models.*' --sarif > pickleprobe.sarifAfter pip install of the kit, the same options work as agentsec pickleprobe …. Every tool also accepts --help and almost every tool accepts --json.
Example output
See the examples under tools/pickleprobe/examples/ in the repository.
SARIF and CI
agentsec pickleprobe path/to/input --sarif > pickleprobe.sarif
# GitHub Actions (composite action ships with the repo)
- uses: dunkleschitz/agentsec@v0
with:
tools: pickleprobe
paths: path/to/input
fail-on: high
SARIF 2.1.0 can be uploaded to GitHub code scanning. The composite action writes per-tool reports under agentsec-sarif/ by default.
OWASP mapping
Heuristic mapping of what this tool helps a reviewer look for. Not an official OWASP endorsement, and not complete coverage of any item. Sources: OWASP Top 10 for LLM Applications 2025 and OWASP Top 10 for Agentic Applications 2026.
- LLM03 Supply Chain — OWASP LLM Top 10 (2025)
- ASI05 Unexpected Code Execution (RCE) — OWASP Agentic Top 10 (2026)
Known limits
- It is a static allowlist/denylist check, not a proof of safety. A file with no PP01 can still
be harmful through an allowed callable used in an unexpected way, or a library class whose own __setstate__ / __reduce__ does something dangerous. Treat PP02 seriously.
STACK_GLOBALnames are resolved by tracking string pushes and the memo. Deliberately
obfuscated pickles that build the names some other way are reported as unresolved (PP02).
- The allowlist is hand-picked for common PyTorch, NumPy and scikit-learn style files. Many
legitimate models (scikit-learn estimators, custom classes) will show PP02 until you --allow their modules.
- Not covered: legacy (pre-1.6) tar-format PyTorch files beyond the first pickle streams,
HDF5 .h5 Keras files, TorchScript code, ONNX/GGUF custom ops, and files over 512 MB inside zip archives (skipped).
- The safest fix is still a format that cannot run code, such as safetensors, plus
torch.load(..., weights_only=True).
Full source, tests, and fixtures: tools/pickleprobe/ in the repository. Install the kit with pip install git+https://github.com/dunkleschitz/agentsec.git.