- Reads
- pickle opcodes
- Answers
- detecting code-execution payloads embedded in Python pickle serialization — it answers 'does loading this file run code', not 'have these weights been tampered with'.
the scanner the HuggingFace Hub runs on upload
5 named scanners, over 179 public models and a labelled cohort of 25 author-flagged backdoored adapters — artifact-only, every tool seeing weights and configs, never documentation. Each tool is measured against the question it was built to answer, not one it never claimed. Where a tool was not run, this page renders nothing.
Source of record: bench/shelf/emit_sot.py · generated 2026-07-28. Every number below reads from that file; none is typed by hand.
Read this first. Every one of these is a competent tool answering its own question well. The benchmark below is not “which tool is best” — it is “which question does each one answer,” because a scanner that never fires on tampered weights may be doing exactly its job, just not the job of catching a backdoor.
They answer “does loading this file run code?” — hunting unsafe pickle opcodes that execute on load. That is a real and important question. It is not “have these weights been tampered with.”
the scanner the HuggingFace Hub runs on upload
load-time-exploit scanner, multi-format
flags non-stdlib imports as LIKELY_UNSAFE, including benign ones
artifact-only here: README/prose withheld (see policy)
Verifies a cryptographic signature a publisher attached. It can only attest what someone signed — and attests nothing when no signature was published.
requires a published Sigstore bundle; no scanned model ships one
Documentation is withheld from every tool because a single-variable control showed a scanner's verdict tracking the prose, not the weights: modelaudit scored an identical backdoored adapter CRITICAL with its README present and clean with it removed, and scored clean weights CRITICAL when handed a card that merely mentions the word 'backdoor'. Its verdict moves with the text in both directions and is uncorrelated with the weights. Configs stay in scope — config.json auto_map remote-code is a real attack surface.
| Arm | Weights | Card | Verdict | What it cited |
|---|---|---|---|---|
| A | BackdoorLLM/Jailbreak_Llama2-7B_BadNets adapter | present | CRITICAL | C&C pattern detected: backdoor — located in README.md |
| B | identical adapter | removed | no findings | — |
| Cconstructed control | clean EleutherAI/pythia-160m | mentions the word | CRITICAL | C&C pattern detected: backdoor — located in README.md |
What this shows: modelaudit's CRITICAL verdict is driven by the README text, not the weights: add prose and it fires, remove prose and it goes silent, on the very same tensors.
Arm C is a CONSTRUCTED control — a statement about the scanner, never about pythia-160m.
Withheld from every tool: README.md, LICENSE, .gitattributes, images, other prose. In scope: weights, configs, tokenizer files — config.json auto_map remote-code is a real attack surface and stays in.
The one cohort with ground truth: 25 adapters published and flagged backdoored by their authors (BackdoorLLM — published, author-labelled backdoored adapters), read beside named clean controls. This is the confusion matrix, denominators on the surface.
| Tool | Backdoor caught | Backdoor missed | Clean flagged | Clean cleared | Nothing to read |
|---|---|---|---|---|---|
| picklescanmmaitre314 | 0 / 0 | 0 / 0 | 0 / 7 | 7 / 7 | 43 / 50 |
| No cohort adapter ships a pickle-format file (safetensors/config only), so this load-time-exploit scanner had nothing to open on any of the poisoned adapters or the clean controls. It neither confirms nor denies weight tampering here. | |||||
| ModelScanProtect AI | 0 / 0 | 0 / 0 | 0 / 7 | 7 / 7 | 43 / 50 |
| Same as picklescan: the cohort ships no scannable serialized file, so ModelScan had nothing to open. Its silence is the absence of a pickle payload, not a judgement on the weights. | |||||
| ficklingTrail of Bits | 0 / 0 | 0 / 0 | 6 / 7 | 1 / 7 | 43 / 50 |
| fickling could only open the 7 clean controls that carry pickle files and flagged 6 of them LIKELY_UNSAFE, while the poisoned adapters (safetensors-only) gave it nothing to read. Its firing tracks benign pickle framework-imports, not the labelled backdoor. | |||||
| modelauditpromptfoo | 0 / 25 | 25 / 25 | 2 / 25 | 23 / 25 | 0 / 50 |
| modelaudit opened all 25 poisoned adapters and 25 clean controls, detecting 0 of the poisoned (fn=25) while firing on 2 clean control(s). On the author-labelled backdoors it is inverted with respect to ground truth — it fires more readily on clean weights than on the backdoored ones. | |||||
| model-signingOpenSSF / Sigstore | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 50 / 50 |
| No cohort model carries a Sigstore bundle, so there is no provenance signature to verify for any of the 50 models. The absence is itself the measurement (unattested provenance) — neither a pass nor a fail. | |||||
The broader read: how often each tool could open a model at all, how often it fired, and — the number that decides whether a positive means anything — how often it fired on a model we independently call clean.
picklescan opened the 34 catalog models that ship a pickle-format file and reported no unsafe opcodes in any. It answers 'does loading this file execute code', not 'have these weights been tampered with', so a null result here is the tool working correctly on its own question — not a clean bill of health for the weights.
ModelScan opened the 34 catalog models carrying a scannable serialized file and found no unsafe operators. Like picklescan it targets load-time code execution rather than weight tampering; the empty result is an accurate answer to that narrower question.
fickling flagged 28 of the 34 openable catalog models as LIKELY_UNSAFE — including 10 of 11 we independently call clean — because it marks any non-standard-library import in a pickle as unsafe by design. On this catalog that fires on ordinary framework serialization (e.g. torch._utils._rebuild_tensor_v2), so its positives do not separate tampered weights from benign ones.
modelaudit scanned 96 catalog models artifact-only (83 exceeded the size cap and were not scanned) and raised a critical finding on 11 — 3 of 34 we call clean and 1 of 15 we flag ourselves. On this catalog its critical verdict does not track weight tampering; the criticals are chiefly standard-serialization patterns (10 of 11), with 1 chat-template heuristic finding.
A benchmark that hides its gaps is not world-class. Every place the coverage is partial is listed here, in the same plain type as the results above.
model-signing was not run over the 180-model catalog; it appears only in the labelled cohort. No catalog model carries a model_signing row.
measured but not present in scanned-models.json; omitted from per_model board.
83 catalog model(s) exceeded the artifact-size cap and were not scanned by modelaudit; they carry no modelaudit row (rendered nothing).
earlier partial cohort run (46 rows) subsumed by cohort-matrix.json (5 tools x 50); used only for cross-check, not as the cohort SoT.
earlier partial cohort run (75 rows) subsumed by cohort-matrix.json (5 tools x 50); used only for cross-check, not as the cohort SoT.
measured but not present in scanned-models.json; omitted from per_model board.
measured but not present in scanned-models.json; omitted from per_model board.
measured but not present in scanned-models.json; omitted from per_model board.
torch.BFloat16Storage reaching importlib.import_module); fickling’s blanket LIKELY_UNSAFE traces to torch._utils._rebuild_tensor_v2. These are properties of ordinary framework files, not evidence of tampering.model-signing has nothing to verify. That is a fact about the ecosystem, not a defect in the tool.This is the shelf, run in the open. Now read what Vulcora does instead.