Scan model filesbefore you load them.
One pickle'd os.system and your inference cluster runs arbitrary code as it loads. EvalGuard parses pickle / SafeTensors / ONNX / GGUF artifacts statically — no deserialization — and surfaces dangerous opcodes, external-data escapes, and obfuscated payloads.
01 · The run
Every byte inspected. None executed.
The scanner walks the artifact's structure statically — opcode by opcode, header by header — and resolves each check to a clean or flagged verdict. Clean means the structure held; flagged means a dangerous opcode or escape was found before any model loaded it.
- Static parse — the artifact is never deserialized or run.
- Finding-level severity counts, persisted for audit.
- Fail your CI on any critical / high finding.
02 · Coverage
Four formats. Static parsing only.
From the pickle opcode stream to GGUF magic bytes — each format has its own structural checks, and not one of them loads the artifact to find them.
Pickle / PyTorch
- REDUCE / NEWOBJ / GLOBAL / STACK_GLOBAL opcodes
- os.system, subprocess, eval, exec imports
- ZIP-wrapped pickles (PyTorch state-dicts)
- Base64 / zlib obfuscation
SafeTensors
- Oversized header (DoS / parser bypass)
- Tensor offset overflow (read-past-EOF exfiltration)
- Metadata schema deviation
ONNX
- External-data path traversal (.. / absolute paths)
- Custom-op imports that load .so / .dll
- Suspicious metadata producer fields
GGUF
- Magic-bytes mismatch
- Metadata length overflow
- Tensor dtype / shape anomalies
03 · Quickstart
Run it from the terminal or the API.
CLI quickstart
Static parse — never deserializes. Runs fully offline against a local model file or directory.
# Scan a local file (no API key required — runs offline) $ evalguard model-scan ./checkpoint.pkl # Scan everything in a directory (recurses automatically) $ evalguard model-scan ./models/ --severity high # JSON output for CI pipelines $ evalguard model-scan ./checkpoint.pkl --format json
API quickstart
Scan by HuggingFace repo, S3 URI, or generic URL. Persisted to model_scans with finding-level severity counts.
# Scan a HuggingFace artifact and persist the result
$ curl -X POST https://evalguard.ai/api/v1/security/model-scan \
-H "Authorization: Bearer $EVALGUARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "<uuid>",
"source": "huggingface",
"sourceRef": "meta-llama/Llama-2-7b-hf/pytorch_model.bin"
}'
# Response:
# {
# "scanId": "<uuid>",
# "format": "pytorch",
# "verdict": "warn",
# "findings": [
# { "severity": "high", "category": "suspicious_import",
# "detail": "Found 'urllib.request.urlopen' in pickle stream" }
# ],
# "stats": { "durationMs": 412, "bytesScanned": 13476283 }
# }04 · Honest limits
Where the scanner stops
- Static parse only. We never deserialize the artifact. That's safer, but means we don't catch payloads that depend on a specific runtime path.
- Heuristic, not formal. A skilled adversary can pickle-via-non-REDUCE; we flag obfuscation hints, but the final call is yours.
- SafeTensors are mostly safe by design. The scanner still runs structural checks, but most findings will be info-level.
- 2 GB cap per URL fetch. Larger artifacts should use the upload variant or stream directly to S3 and scan via the S3 source.