Skip to content

fix(pytorch): trust referenced storage members#1654

Open
mldangelo-oai wants to merge 31 commits into
mainfrom
mdangelo/codex/hf-fp-t26-pytorch-storage-member-discovery-20260610
Open

fix(pytorch): trust referenced storage members#1654
mldangelo-oai wants to merge 31 commits into
mainfrom
mdangelo/codex/hf-fp-t26-pytorch-storage-member-discovery-20260610

Conversation

@mldangelo-oai

Copy link
Copy Markdown
Contributor

Summary

Fixes a PyTorch ZIP false positive where raw tensor storage members under data/<key> were discovered as hidden pickle streams even when the same-prefix data.pkl referenced them through canonical PyTorch storage persistent IDs.

The scanner now derives validated storage members from bounded data.pkl parsing before generic embedded-pickle discovery. Referenced tensor storage bytes are skipped as pickle streams; unreferenced extensionless members, mismatched/missing keys, noncanonical persistent IDs, truncated data.pkl, and ambiguous layouts remain scanned or explicitly inconclusive.

This preserves hidden-pickle detection for attacker-controlled sidecars while avoiding opaque extension/parse-incomplete findings on legitimate tensor bytes such as pinned Whisper pytorch_model/data/379.

Validation

  • uv run ruff format --check modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/
  • uv run ruff check modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/
  • uv run mypy modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/
  • uv run pytest -n auto -m "not slow and not integration" --maxfail=1

Additional checks:

  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest packages/modelaudit-picklescan/tests/test_api.py -k "storage_blob or storage_lookalike or hidden_pytorch_zip_pickle_member" --tb=short — 8 passed
  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest tests/scanners/test_pytorch_zip_scanner.py -k "pytorch_zip_discovery" --tb=short — 12 passed
  • PROMPTFOO_DISABLE_TELEMETRY=1 HF_HOME=/home/dev-user/code/modelaudit-fp-fixes/20260611T005000Z-hf-fp-followup/task-26-pytorch-storage-member-discovery/artifacts/hf-cache uv run pytest tests/scanners/test_pytorch_zip_scanner.py::test_pytorch_zip_pinned_whisper_storage_member_qa --tb=short -s — 1 passed
  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest packages/modelaudit-picklescan/tests/test_api.py --tb=short — 639 passed
  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest tests/scanners/test_pytorch_zip_scanner.py -m "not slow and not integration" --tb=short — 839 passed, 1 deselected
  • cargo fmt --manifest-path Cargo.toml -- --check
  • cargo check --manifest-path Cargo.toml
  • cargo clippy --manifest-path Cargo.toml --all-targets -- -D warnings
  • cargo test --manifest-path Cargo.toml — 164 passed
  • uv lock --check from packages/modelaudit-picklescan/
  • git diff --check

Pinned real-model QA:

  • Reproduced the issue before the fix with openai/whisper-large-v3 at revision 06f233fe06e710322aca913c1bc4249a0d71fce1 using exact remote bytes for pytorch_model/data.pkl and pytorch_model/data/379. Before the fix, standalone picklescan reported pickle_files=('pytorch_model/data.pkl', 'pytorch_model/data/379') plus EXTENSION_REF / parse_incomplete evidence for pytorch_model/data/379.
  • After the fix, the same bounded pinned-member artifact reports pickle_files=('pytorch_model/data.pkl',) and no pytorch_model/data/379 findings or notices.
  • The committed slow QA test fetches only the ZIP central directory plus pytorch_model/data.pkl, pytorch_model/version, pytorch_model/byteorder, and pytorch_model/data/379 via HTTP ranges. It intentionally reports an inconclusive missing-storage outcome for the sliced artifact while proving data/379 is no longer scanned as a pickle stream.

Post-rebase broad suite on origin/main 8d6c4864:

  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest -n auto -m "not slow and not integration" --maxfail=1 — 17,376 passed, 1,292 skipped, 39 warnings

Checklist

  • I followed the security-first guidelines in AGENTS.md.
  • I did not weaken detection behavior.
  • I added/updated tests when behavior changed.
  • I updated docs where needed.

@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

@codex review

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Workflow run and artifacts

Performance Benchmarks

Compared 12 shared benchmarks with a regression threshold of 15%.
Status: 2 regressions, 0 improved, 10 stable, 0 new, 0 missing.
Aggregate shared-benchmark median: 1.454s -> 1.605s (+10.4%).

Top regressions:

  • tests/benchmarks/test_scan_benchmarks.py::test_scan_single_checkpoint_before_load +19.7% (71.75ms -> 85.90ms, single-checkpoint-preflight, single_checkpoint.pkl, size=183.0 KiB, files=1)
  • tests/benchmarks/test_scan_benchmarks.py::test_scan_duplicate_registry_snapshot +19.4% (394.76ms -> 471.39ms, duplicate-heavy-registry, registry-snapshot, size=915.2 KiB, files=13)
Workload Benchmark Target Size Files Baseline Current Change Status
single-checkpoint-preflight tests/benchmarks/test_scan_benchmarks.py::test_scan_single_checkpoint_before_load single_checkpoint.pkl 183.0 KiB 1 71.75ms 85.90ms +19.7% regression
duplicate-heavy-registry tests/benchmarks/test_scan_benchmarks.py::test_scan_duplicate_registry_snapshot registry-snapshot 915.2 KiB 13 394.76ms 471.39ms +19.4% regression
suspicious-pickle-intake tests/benchmarks/test_scan_benchmarks.py::test_scan_suspicious_pickle_intake suspicious-intake 183.8 KiB 4 142.84ms 157.96ms +10.6% stable
mixed-model-repository tests/benchmarks/test_scan_benchmarks.py::test_scan_release_candidate_repository release-candidate 547.3 KiB 32 493.47ms 538.35ms +9.1% stable
warm-cache-rescan tests/benchmarks/test_scan_benchmarks.py::test_scan_warm_cached_repository_rescan release-candidate 547.3 KiB 32 123.25ms 127.89ms +3.8% stable
nested-payload-review tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_nested_payload_review[nested_hex] nested_hex 130 B 1 604.6us 621.4us +2.8% stable
clean-training-checkpoint tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_clean_training_checkpoint safe_large 278.2 KiB 1 111.39ms 108.47ms -2.6% stable
nested-payload-review tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_nested_payload_review[nested_raw] nested_raw 78 B 1 574.1us 585.0us +1.9% stable
padded-multi-stream-upload tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_padded_multi_stream_upload multi_stream_padded 4.1 KiB 1 638.1us 646.7us +1.4% stable
direct-malicious-upload tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_direct_malicious_upload malicious_reduce 52 B 1 525.0us 531.7us +1.3% stable
chunked-upload-stream tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_chunked_upload_stream chunked_stream 278.2 KiB 1 113.74ms 112.53ms -1.1% stable
nested-payload-review tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_nested_payload_review[nested_base64] nested_base64 98 B 1 582.8us 584.4us +0.3% stable

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mldangelo-oai mldangelo-oai requested a review from mldangelo June 11, 2026 02:01
@mldangelo-oai mldangelo-oai enabled auto-merge (squash) June 11, 2026 02:01
@mldangelo-oai mldangelo-oai disabled auto-merge June 11, 2026 04:05

Copy link
Copy Markdown
Contributor Author

New pinned HF QA blocks auto-merge. On main 8d6c4864, nvidia/PiD@b4887b3c8fc65277a9b7a084292bf9fc0778c5ac routes referenced PyTorch ZIP storage member siglip2_decoder/data/356 through pickle probing and emits warning S902 (“FRAME length crosses STOP”) plus warning S901 parse failure. This appears to be raw tensor storage, not an executable pickle stream.

Auto-merge is disabled pending exact-head selected-file QA. @codex reproduce this single referenced member without downloading unrelated weights; prove the current branch suppresses the storage false positive while still detecting hidden/unreferenced malicious pickle members, malformed storage references, concatenated pickle streams, and disguised executable payloads. Post the pinned hash/results and request exact-head review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4ee93a01e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread modelaudit/scanners/pytorch_zip_scanner.py Outdated
Comment thread tests/scanners/test_pytorch_zip_scanner.py Outdated
@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

Independent adversarial review: promptfoo/modelaudit PR #1654

Findings

P1 — Storage trust accepts provenance that PyTorch itself rejects, suppressing malicious members

References:

  • modelaudit/scanners/pytorch_zip_scanner.py:1268-1283 skips every member returned by the new trust helper before pickle probing.
  • modelaudit/scanners/pytorch_zip_scanner.py:1851-1875 normalizes backslashes and leading slashes before matching storage members.
  • modelaudit/scanners/pytorch_zip_scanner.py:1912-1939 trusts every referenced key that intersects the normalized member map.
  • modelaudit/scanners/pytorch_zip_scanner.py:2010-2029 accepts any torch.<name ending in Storage> global and only requires a PID tuple of length at least three.
  • The standalone copy has the same behavior at packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py:581-590, :622-643, :680-710, :859-860, and :904-919.

The trust predicate is materially weaker than PyTorch's loader semantics. Two exact-head adversarial cases demonstrate false negatives:

  1. A canonical archive used ('storage', torch.FakeStorage, '0', 'cpu', 33) and placed a protocol-0 posix.system pickle at archive/data/0. Stock torch.load(..., weights_only=False) rejected the archive with AttributeError: Can't get attribute 'FakeStorage'. Base 8d6c4864 discovered archive/data/0 and emitted three critical S201 findings. Head cf16a845 returned success=True, listed only archive/data.pkl, and emitted no issue. Standalone changed from MALICIOUS on the base behavior to SUSPICIOUS with no DANGEROUS_CALL finding.
  2. A canonical data.pkl referenced key 0, but the only payload member was the noncanonical ZIP name archive\data\0. Stock PyTorch rejected it with RuntimeError: file in archive is not in a subdirectory archive/. The exact-head main scanner nevertheless normalized the name, returned success=True, listed only archive/data.pkl, and suppressed the embedded posix.system pickle. Standalone similarly omitted the member. A leading-slash alias (/archive/data/0) is also trusted and omitted by standalone scan routing.

This is not the same as requiring every loader-valid tensor blob to be scanned as a pickle. Canonical, valid storage bytes are inert raw storage and may legitimately look like pickle or ELF data. The bug is that this implementation treats malformed descriptors and noncanonical member aliases as loader-valid provenance.

Required fix: make trust match the loader rather than string shape. Use exact canonical ZIP member names for provenance (no slash/backslash aliasing), validate the complete persistent-ID shape and scalar fields, and restrict storage globals to the known PyTorch storage classes/modules. Any malformed descriptor or noncanonical member must remain on the generic probe path. Add mirrored main/standalone regressions for torch.FakeStorage, a single-backslash member, a leading-slash member, malformed tuple shapes, and normalized-name collisions.

P2 — Valid torch.storage.UntypedStorage archives remain false positives

References:

  • modelaudit/scanners/pytorch_zip_scanner.py:2015-2020 and packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py:909-914 require storage_type.module == "torch".
  • Existing ModelAudit parsing explicitly recognizes UntypedStorage and both valid modules at modelaudit/scanners/pickle_scanner.py:70-83 and :813-820 ({"torch", "torch.storage"}).

I built a minimal loadable PyTorch ZIP using persistent ID ('storage', torch.storage.UntypedStorage, '0', 'cpu', 148) and the same 148-byte pickle-looking prefix used by the PR's PiD fixture. PyTorch 2.11 loaded it successfully as a 148-byte storage and preserved the exact first 20 bytes. Both exact-head scanners failed to recognize the reference, probed archive/data/0, and reproduced the false-positive family this PR is intended to remove:

  • Main scanner: pickle_files=['archive/data.pkl', 'archive/data/0'], S902 FRAME-overrun warning, S902 parse-incomplete notice, S901 parse-failure warning, and an inconclusive result.
  • Standalone scanner: pickle_files included archive/data/0, verdict SUSPICIOUS, status INCONCLUSIVE, and a STRUCTURAL_TAMPER finding.

Required fix: share or mirror the repository's known storage-class allowlist, including torch.storage.UntypedStorage, while still rejecting nonexistent suffix matches such as torch.FakeStorage. Add a loadability-backed regression for this archive variant in both scanner suites.

P2 — The new trust pass bypasses the existing opcode budget and can allocate far beyond its 10 MiB input cap

References:

  • The new passes are unconditional before member discovery at modelaudit/scanners/pytorch_zip_scanner.py:1268-1272 and packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py:581-585.
  • The byte cap is 10 MiB at modelaudit/scanners/pytorch_zip_scanner.py:313,1880-1891 and packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py:87,648-667.
  • Both parsers first materialize list(pickletools.genops(...)) and then maintain unbounded Python stack/memo structures at modelaudit/scanners/pytorch_zip_scanner.py:1985-2111 and packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py:878-1001.
  • The normal standalone scanner has a 1,000,000-opcode default budget (packages/modelaudit-picklescan/src/modelaudit_picklescan/options.py:11,25), but this pre-scan trust parser does not consume it.

Measured on exact head with a valid protocol-4 stream made of EMPTY_TUPLE opcodes:

  • 65,539 input bytes: 7,148,648 bytes peak Python allocation.
  • 262,147 input bytes: 28,736,552 bytes peak.
  • 1,048,579 input bytes: 113,360,808 bytes peak in each duplicated helper; the main helper took 2.68 s and the standalone helper 0.93 s.
  • A 4 MiB archive scan reached about 913 MB process RSS and 7.74 s on head, versus about 846 MB and 6.45 s on base, before the normal scanner finally returned opcode_budget_exceeded.

The explicit 10 MiB acceptance threshold therefore permits millions of Python objects and a second full parse before the existing bounded scanner can stop. The scanner timeout is not checked inside this parser.

Required fix: stream pickletools.genops, apply the configured/default opcode ceiling during trust extraction, and cap stack depth, memo entries, tuple width, and referenced-key count. A limit hit must return no trusted members plus an explicit incomplete/inconclusive notice. Add focused resource-limit tests to both package copies.

P2 — The committed pinned integration test does not cover the nvidia/PiD blocker

References:

  • tests/scanners/test_pytorch_zip_scanner.py:30-39 pins openai/whisper-large-v3@06f233fe..., pytorch_model.bin, and pytorch_model/data/379.
  • tests/scanners/test_pytorch_zip_scanner.py:539-563 exercises only that Whisper slice.
  • The live blocker recorded on PR fix(pytorch): trust referenced storage members #1654 is nvidia/PiD@b4887b3c8fc65277a9b7a084292bf9fc0778c5ac, file checkpoints/scale_rae/decoder/siglip2_sop14_i224_web73M_ganw3_decXL.pt, member siglip2_decoder/data/356.

The new slow test can pass while the selected-file regression that disabled auto-merge regresses. This is the current unresolved P2 review thread and it is valid.

I independently performed the missing QA using bounded HTTP ranges, so the implementation behavior at this head is known even though the committed regression is wrong:

  • Pinned file size: 1,662,529,538 bytes; source revision response: b4887b3c8fc65277a9b7a084292bf9fc0778c5ac.
  • siglip2_decoder/data.pkl: 88,413 bytes, SHA-256 8ee22aac231310f794cd58537e4d6d19f08ab32a99a573d18e0f0d2fcfa57bd8.
  • siglip2_decoder/data/356: 5,308,416 bytes, SHA-256 23c4f4d8d8546f94994eebaf5a4dbe5b7eb963e69000f71ba491b673591d0f20.
  • Four-member QA slice: 5,397,355 bytes, SHA-256 00549ae64d4a119185b7c089f3baec86565955222d50e34f1b0aa56a47914ccf.
  • Base main scanner: pickle_files contained data.pkl and data/356; reproduced S902 FRAME-overrun, S902 parse-incomplete, S901 parse-failure, and JIT incomplete findings.
  • Head main scanner: pickle_files=['siglip2_decoder/data.pkl']; no data/356 issue/check. The slice was correctly inconclusive only because its other 455 referenced storage members were intentionally absent.
  • Base standalone: data/356 was scanned and produced STRUCTURAL_TAMPER; head standalone listed only data.pkl and emitted no data/356 finding.

Required fix: replace or supplement the Whisper test with the exact PiD revision/file/member and immutable size/hash assertions. Keep the bounded central-directory/member-range approach.

Exact target and live state

  • Metadata fetched: 2026-06-11T05:03:31Z.
  • PR: https://github.com/promptfoo/modelaudit/pull/1654
  • Title: fix(pytorch): trust referenced storage members
  • State: open, non-draft.
  • Exact reviewed head: cf16a845fda70e8ce241d4aad20d4af8f4d3292a.
  • Live base SHA: 8d6c4864fe2ea833ceaef1b9803d225afb1e8d69 (main).
  • Head was fetched from refs/pull/1654/head and independently verified to match GitHub headRefOid twice, including after testing.
  • GitHub reports MERGEABLE but mergeStateStatus=BLOCKED.
  • Two current, unresolved, non-outdated review threads remain: referenced-storage probing and the incorrect pinned QA target.
  • At the final refresh, completed CodeQL, Docker, lint/format, type-check, package build, Python 3.13, and standalone-package 3.10-3.13 jobs were green. Quick Feedback 3.12, Windows 3.11, and Python 3.10 were still in progress.

The earlier automated review summary was based on commit c4ee93a01e4d5b6454eba111ff90e4d7d342e004; this report reviewed the superseding cf16a845 head.

Verification performed

  • Read root AGENTS.md and scoped packages/modelaudit-picklescan/AGENTS.md from the exact head.
  • Inspected the complete six-file diff and surrounding scanner, archive-validation, pickle-routing, and test code.
  • packages/modelaudit-picklescan/tests/test_api.py -k "storage_blob or storage_lookalike or hidden_pytorch_zip_pickle_member" --tb=short -q: 8 passed, 631 deselected.
  • tests/scanners/test_pytorch_zip_scanner.py -k "pytorch_zip_discovery" --tb=short -q: 12 passed, 828 deselected.
  • git diff --check 8d6c4864...cf16a845: clean.
  • Exact-head source worktree remained clean; no source files were modified.
  • Adversarial controls exercised in both main and standalone paths: canonical referenced raw storage, unreferenced malicious storage, canonical referenced protocol-0 posix.system, concatenated N. plus malicious pickle, fake storage global, missing+existing keys, single-backslash member alias, leading-slash member alias, valid torch.storage.UntypedStorage, and high-opcode bounded input.
  • Real PiD before/after QA used exact member bytes reconstructed by range requests; no unrelated model weights were downloaded.

Review-thread disposition

  • “Keep probing referenced storage members”: the literal recommendation is overbroad. A canonical, loader-valid referenced storage member is raw tensor storage, and arbitrary bytes inside it are not executed as pickle; probing every such member recreates the false positive. The underlying trust concern is nevertheless valid because the implementation accepts provenance PyTorch rejects. Finding P1 above narrows the fix to exact loader-valid provenance rather than unconditional probing.
  • “Point QA at the blocked pinned member”: validated. The committed test targets Whisper, not the PiD blocker. Independent exact-head PiD QA passes, but durable source coverage is still missing.

Merge disposition

Request changes / do not merge cf16a845.

The exact PiD false positive is fixed at runtime, and the focused existing tests pass, but the new trust boundary is not sound: it suppresses malicious members for invalid storage globals and noncanonical ZIP names, rejects a valid torch.storage.UntypedStorage variant, and performs an unbounded opcode/materialization pass under a 10 MiB byte-only cap. The committed pinned test also does not protect the actual PiD regression. Re-review should occur at the next exact head after the trust predicate, resource limits, and regression coverage are corrected in both duplicated implementations.

@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

Pinned PiD QA on exact head 9d833e2d1c90c24039a9a1857c83bf9f959fdc6c:

  • Source: nvidia/PiD@b4887b3c8fc65277a9b7a084292bf9fc0778c5ac, checkpoints/scale_rae/decoder/siglip2_sop14_i224_web73M_ganw3_decXL.pt
  • Bounded fetch only: ZIP tail + central directory + local headers + siglip2_decoder/data.pkl, siglip2_decoder/version, siglip2_decoder/byteorder, siglip2_decoder/data/356
  • Source size: 1,662,529,538 bytes; selected slice: 5,397,355 bytes, sha256 cc8ddcb749fa43a62b408e769376899c1c790c226b3aa41aa853abe76b333761; range bytes fetched including ZIP metadata: 13,817,669
  • Member hashes: data.pkl 8ee22aac231310f794cd58537e4d6d19f08ab32a99a573d18e0f0d2fcfa57bd8 (88,413 bytes); data/356 23c4f4d8d8546f94994eebaf5a4dbe5b7eb963e69000f71ba491b673591d0f20 (5,308,416 bytes)

Before, on main 8d6c4864fe2ea833ceaef1b9803d225afb1e8d69:

  • Scanner: pytorch_zip; pickle_files: siglip2_decoder/data.pkl, siglip2_decoder/data/356
  • data/356 emitted S902 (Pickle FRAME length crosses the STOP opcode) and S901 (Pickle parsing failed before full scan completion), plus JIT S510
  • Aggregate exit code: 1; aggregate issue codes: S212, S510, S901, S902; scan errors: 0

After, on exact PR head 9d833e2d1c90c24039a9a1857c83bf9f959fdc6c:

  • Scanner: pytorch_zip; pickle_files: siglip2_decoder/data.pkl only
  • No S901/S902 and no issue/check for siglip2_decoder/data/356
  • Aggregate exit code remains 1 only because this selected-member slice still surfaces S212 on data.pkl plus JIT S510; scan errors: 0
  • Direct result is intentionally inconclusive for the partial slice: pytorch_zip_storage_reference_missing_members (455 omitted storage keys) and jit_embedded_python_snippet_limit

Review follow-up controls added/kept:

  • Referenced raw storage with the pinned PiD FRAME-over-STOP prefix is not routed as pickle.
  • Referenced storage that is actually a parse-confirmed protocol-0 or binary malicious pickle is still routed and detected.
  • Unreferenced/hidden malicious pickle members, malformed/missing/fake storage references, protocol-0/text pickle, binary pickle opcodes, follow-on/trailing streams, renamed/nested ZIP payloads, disguised executable content, and incomplete probe paths all stay covered.
  • Slow HF QA now targets the exact PiD blocker/member instead of Whisper.

Local validation:

  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest -q ... focused storage/PiD set: 15 passed
  • Broader malicious-control set: 44 passed
  • uv run ruff format --check modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/: clean
  • uv run ruff check modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/: clean
  • uv run mypy modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/: clean
  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest -n auto -m "not slow and not integration" --maxfail=1: 17,380 passed, 1,292 skipped, 39 warnings
  • git diff --check: clean

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9d833e2d1c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread modelaudit/scanners/pytorch_zip_scanner.py Outdated
@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

Independent adversarial rereview: promptfoo/modelaudit PR #1654

Snapshot: 2026-06-11T05:50:49Z

Findings

P1 — Referenced storage members with a first pickle stream larger than 4 KiB bypass both scanners

References:

  • modelaudit/scanners/pytorch_zip_scanner.py:1287-1288 routes provenance-matched members to the new trusted-storage probe instead of the existing bounded pickle-prefix path.
  • modelaudit/scanners/pytorch_zip_scanner.py:1667-1676 reads at most 4 KiB and requires a complete stream/FRAME before adding the member to pickle_files.
  • packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py:598-605 and :795-803 duplicate the same routing and 4 KiB gate.
  • tests/scanners/test_pytorch_zip_scanner.py:504-522 covers only short, complete malicious payloads and does not exercise a first frame/body extending beyond 4 KiB.

Exact-head reproduction used a canonical PyTorch ZIP whose referenced archive/data/0 was an 8.3 KiB protocol-4 pickle containing a dangerous eval reduction after an 8 KiB byte string:

  • Base 8d6c4864, main scanner: pickle_files included archive/data/0; critical dangerous finding.
  • Base standalone scanner: verdict malicious; DANGEROUS_CALL at position 8299.
  • Head 9d833e2d, main scanner: pickle_files=['archive/data.pkl'], success=True, no data/0 issue.
  • Head standalone scanner: pickle_files=['archive/data.pkl'], no DANGEROUS_CALL.

This is introduced by the PR. An unreferenced copy of the same 8.3 KiB payload remains detected by both scanners, proving the regression is the new trusted-member branch rather than the underlying scanner.

The prior provenance blocker is only partially addressed. The parser still normalizes noncanonical member paths at modelaudit/scanners/pytorch_zip_scanner.py:1908-1912 and packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py:631, and still accepts any torch.*Storage descriptor with only three tuple fields at main :2067-2077 and standalone :959-969. Exact-head torch.FakeStorage and backslash-alias archives reached the weaker 4 KiB path and omitted the same large malicious member; PyTorch 2.11 rejected those archives. Base detected those payloads.

Required fix: do not require a referenced member's first pickle stream to complete inside 4 KiB. Route structurally valid pickle prefixes to the existing bounded full scanner, or stream the probe with the same opcode/resource limits and explicit fail-closed notices. Make provenance match PyTorch loader semantics: exact canonical member names, exact persistent-ID shape/scalar validation, and a known storage-class/module allowlist. Add mirrored main/standalone tests for a first frame/body beyond 4 KiB, torch.FakeStorage, backslash and leading-slash aliases, malformed tuple shapes, and concatenated streams.

P2 — The new storage-reference pre-pass bypasses opcode, tracked-state, and timeout bounds

References:

  • Main accepts data.pkl up to 10 MiB at modelaudit/scanners/pytorch_zip_scanner.py:1936-1955, then materializes every opcode with list(pickletools.genops(...)) at :2043 and maintains unbounded stack/memo state from :2047-2088.
  • Standalone does the same at packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py:656-689 and :933-976.
  • The main scanner checks its timeout before and after discovery, not inside this parser. The standalone normal scanner's opcode/tracked-state limits run only after this pre-pass.

Measured on exact head with valid protocol-4 EMPTY_TUPLE streams:

Input bytes Main peak allocation Standalone peak allocation
65,539 7,149,128 7,148,344
262,147 28,736,984 28,736,200
1,048,579 113,361,240 113,360,456

An actual 1,048,579-byte archive scan configured with a 0.01-second main timeout took 0.73 seconds before returning opcode_budget_exceeded and pytorch_zip_scan_timeout. Standalone took 0.32 seconds and only then emitted its normal opcode/tracked-state notices. This parser and its amplification are introduced by the PR.

Required fix: stream genops; enforce the configured/default opcode ceiling, stack depth, memo-entry count, tuple width, and referenced-key count; check the main timeout inside the loop. Any limit must return no trusted members plus an explicit inconclusive notice. Add mirrored bound tests for both package copies.

P2 — Valid torch.storage.UntypedStorage archives remain in the target false-positive family

References:

  • Main requires storage_type.module == "torch" at modelaudit/scanners/pytorch_zip_scanner.py:2073-2077.
  • Standalone has the same restriction at packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py:965-969.
  • Existing ModelAudit parsing already recognizes UntypedStorage and both torch / torch.storage modules at modelaudit/scanners/pickle_scanner.py:64-82 and :813-816.

I built a minimal archive with persistent ID ('storage', torch.storage.UntypedStorage, '0', 'cpu', 148) and the PiD pickle-looking storage prefix. PyTorch 2.11 loaded it successfully as a 148-byte storage.

Both head scanners still routed archive/data/0 as pickle:

  • Main: pickle_files=['archive/data.pkl','archive/data/0'], structural/persistent-ID findings, and inconclusive pickle_analysis_incomplete.
  • Standalone: pickle_files included data/0, verdict suspicious, STRUCTURAL_TAMPER, and oversized-frame/parse-incomplete notices.

This behavior is pre-existing: base produced the same result. It is therefore not a regression introduced by this diff, but it is a concrete incomplete implementation of this PR's stated false-positive fix and the prior blocker remains unresolved.

Required fix: share or mirror the repository's storage-class allowlist, including torch.storage.UntypedStorage, while rejecting suffix-only names such as torch.FakeStorage. Add a loadability-backed regression in both scanner suites.

Prior blocker reconciliation

Prior blocker Exact-head result
Unsound PyTorch storage provenance Unresolved. Small malicious referenced pickles are now probed, but suffix-based/normalized provenance still feeds loader-invalid archives into the weaker 4 KiB path.
Valid UntypedStorage false positives Unresolved, but confirmed pre-existing rather than introduced by this PR.
Opcode/resource bounds Unresolved. The new duplicated pre-pass still uses list(genops) and unbounded stack/memo state.
Wrong pinned regression Fixed. The committed slow test now targets the exact PiD revision, file, and siglip2_decoder/data/356; it passed.

Pinned checkpoint QA

Source:

  • Repository/revision: nvidia/PiD@b4887b3c8fc65277a9b7a084292bf9fc0778c5ac
  • File: checkpoints/scale_rae/decoder/siglip2_sop14_i224_web73M_ganw3_decXL.pt
  • Bounded slice: central directory/local headers plus data.pkl, version, byteorder, and data/356.

Independent member evidence:

  • siglip2_decoder/data.pkl: 88,413 bytes; SHA-256 8ee22aac231310f794cd58537e4d6d19f08ab32a99a573d18e0f0d2fcfa57bd8.
  • siglip2_decoder/data/356: 5,308,416 bytes; SHA-256 23c4f4d8d8546f94994eebaf5a4dbe5b7eb963e69000f71ba491b673591d0f20.
  • Rebuilt four-member slice: 5,397,355 bytes. Its ZIP hash is intentionally not stable because writestr timestamps vary; member hashes are stable.

Exact-head results:

  • Main: pickle_files=['siglip2_decoder/data.pkl']; no issue/check for data/356.
  • Standalone: pickle_files=['siglip2_decoder/data.pkl']; no data/356 finding.
  • Both remained inconclusive for the intentionally partial slice because 455 referenced storage members were omitted; main also hit the bounded JIT snippet limit.
  • Committed pinned integration test: 1 passed.

Other adversarial controls

  • Canonical short malicious referenced pickles: detected by both scanners.
  • Canonical concatenated stream with an early benign pickle and later large malicious stream: detected by both scanners.
  • Unreferenced large malicious storage-lookalike: detected by both scanners.
  • Truncated data.pkl: malicious storage member detected; scan marked inconclusive.
  • Missing referenced key: malicious existing member detected; missing-member outcome recorded.
  • torch.FakeStorage and backslash aliases with a large malicious member: missed on head, detected on base; PyTorch rejected both archives.
  • Leading-slash alias: PyTorch rejected it; standalone head omitted the large malicious member that base detected.
  • Executable sidecar provenance suppression was reproduced on head and base and is therefore pre-existing, not attributed to this diff.

Focused validation

  • packages/modelaudit-picklescan/tests/test_api.py -k 'storage_blob or storage_lookalike or hidden_pytorch_zip_pickle_member': 10 passed.
  • tests/scanners/test_pytorch_zip_scanner.py -k 'pytorch_zip_discovery': 14 passed.
  • Exact pinned PiD test: 1 passed.
  • Ruff format check: 419 files already formatted.
  • Ruff check: all checks passed.
  • Focused mypy on the four changed Python source/test files: success.
  • git diff --check 8d6c4864...HEAD: clean.
  • Full local mypy on macOS reported five os.listxattr/setxattr/getxattr typing errors in modelaudit/cli.py and tests/test_cli.py; base reports the same five errors (and a mypy internal error), so these are pre-existing/platform-specific. Exact-head CI Type Check is green.
  • Isolated head worktree remained clean and detached.

Security scan artifacts:

  • Markdown: /tmp/codex-security-scans/modelaudit/9d833e2d1c90c24039a9a1857c83bf9f959fdc6c_20260611T053000Z/report.md
  • HTML: /tmp/codex-security-scans/modelaudit/9d833e2d1c90c24039a9a1857c83bf9f959fdc6c_20260611T053000Z/report.html

Exact target, CI, and review state

  • PR: promptfoo/modelaudit#1654, open, non-draft.
  • Exact reviewed head: 9d833e2d1c90c24039a9a1857c83bf9f959fdc6c.
  • Exact base: 8d6c4864fe2ea833ceaef1b9803d225afb1e8d69 (main).
  • Merge base equals the live base; branch is not behind.
  • GitHub: MERGEABLE, mergeStateStatus=BLOCKED, reviewDecision=REVIEW_REQUIRED.
  • CI: 28 checks passed, 4 intentionally skipped, 0 pending, 0 failed. Python CI, Docker, Windows, Python 3.10/3.12/3.13, standalone 3.10-3.13, Type Check, Ruff, CodeQL, package build, dependency audit, and benchmarks are green.
  • Performance workflow: 0 regressions across 12 shared benchmarks; aggregate median +1.0%.

Review threads:

  • 3 total, all technically unresolved in GitHub.
  • 1 current/non-outdated exact-head P1 at modelaudit/scanners/pytorch_zip_scanner.py:1676: large referenced storage pickles are omitted by the 4 KiB path. Independently validated here.
  • 2 outdated threads on cf16a845: complete skipping of referenced members and the wrong Whisper pinned target. The latest head partially supersedes the first and fixes the second.

No GitHub comments, reviews, labels, approvals, or other state were posted or changed.

Merge disposition

DO NOT MERGE / request changes.

The exact head is CI-green and the PiD false positive is fixed, but the PR introduces a deterministic security-detection regression for larger referenced pickle streams and an unbounded pre-scan parser. It also leaves the known valid UntypedStorage case in the false-positive family the PR is intended to resolve.

Before merge:

  1. Remove the 4 KiB complete-stream false-negative path and add mirrored large-pickle/provenance regressions.
  2. Bound the storage-reference parser's opcode and state growth in both implementations.
  3. Add loadability-backed torch.storage.UntypedStorage coverage.
  4. Re-run the exact PiD slice, adversarial controls, focused suites, and exact-head CI.
  5. Resolve the current exact-head P1 thread and request a new independent review on the replacement head.

@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

Exact-head follow-up for the PiD storage-member blocker is pushed at 8804e039ee2938d0a7612592bf4b579f0282c861.

Pinned bounded QA:

  • Source: nvidia/PiD@b4887b3c8fc65277a9b7a084292bf9fc0778c5ac, checkpoints/scale_rae/decoder/siglip2_sop14_i224_web73M_ganw3_decXL.pt, source size 1,662,529,538 bytes.
  • Selected-only slice: siglip2_decoder/data.pkl, siglip2_decoder/version, siglip2_decoder/byteorder, siglip2_decoder/data/356; slice size 5,397,355, sha256 cc8ddcb749fa43a62b408e769376899c1c790c226b3aa41aa853abe76b333761.
  • Member hashes: data.pkl 88,413 bytes sha256 8ee22aac231310f794cd58537e4d6d19f08ab32a99a573d18e0f0d2fcfa57bd8; data/356 5,308,416 bytes sha256 23c4f4d8d8546f94994eebaf5a4dbe5b7eb963e69000f71ba491b673591d0f20.
  • Main 8d6c4864: pickle_files included siglip2_decoder/data.pkl and siglip2_decoder/data/356; codes S212, S510, S901, S902; data/356 emitted S902 frame-over-STOP and S901 parse-incomplete; aggregate exit 1, scan errors 0.
  • Exact head 8804e039: pickle_files is only siglip2_decoder/data.pkl; no issue/check for siglip2_decoder/data/356; direct codes S212, S510; scan outcome inconclusive for pytorch_zip_storage_reference_missing_members and jit_embedded_python_snippet_limit; aggregate exit 1, aggregate errors false.

Follow-up fixes in this head:

  • Trust uses exact canonical ZIP member names; no leading-slash/backslash normalization aliases.
  • Trust parser now requires complete 5-field PyTorch storage persistent IDs, known torch/torch.storage storage classes, valid location, non-bool nonnegative size, and bounded streaming opcode/stack/memo/tuple/reference limits.
  • torch.storage.UntypedStorage is accepted as valid storage; torch.FakeStorage/synthetic storage names are not.
  • Limit/malformed trust parsing returns no trusted storage member and records inconclusive validation, so hidden payload discovery still runs.

Malicious controls covered:

  • Referenced storage containing protocol-0 posix.system and binary pickle payloads is still scanned and reported.
  • Unreferenced numeric storage lookalikes still scan.
  • Backslash storage aliases scan; leading-slash aliases are blocked as S405 path traversal.
  • Fake storage globals, malformed bool-sized tuples, extra-field tuples, and opcode-budget exhaustion do not suppress hidden storage payloads.

Validation:

  • cargo fmt --manifest-path packages/modelaudit-picklescan/Cargo.toml -- --check
  • cargo check --manifest-path packages/modelaudit-picklescan/Cargo.toml
  • cargo clippy --manifest-path packages/modelaudit-picklescan/Cargo.toml --all-targets -- -D warnings
  • cargo test --manifest-path packages/modelaudit-picklescan/Cargo.toml (164 passed)
  • Focused root/package storage tests (26 passed, then fake-storage slice 14 passed)
  • Nearby root/package suite: 1497 passed, 1 deselected, 5 warnings
  • uv run ruff format ...; uv run ruff check --fix ...; uv run ruff format --check ...; uv run ruff check ...
  • uv run mypy modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/
  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest -n auto -m "not slow and not integration" --maxfail=1 (17395 passed, 1292 skipped, 39 warnings)
  • git diff --check HEAD~1 HEAD

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8804e039ee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread modelaudit/scanners/pytorch_zip_scanner.py Outdated
Comment thread packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py Outdated
@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

Follow-up for the live P1 review thread is pushed at 853404d4d189c407d06726078ab8e41771008508.

Change:

  • Referenced storage members with binary pickle protocol prefixes now fail open to the full scanner when the bounded trusted-storage probe parses as an incomplete prefix. This covers large framed pickles whose frame/body extends past the 4 KiB trust probe.
  • Proto-0-looking referenced storage still requires complete confirmation, preserving the pinned PiD raw tensor-storage false-positive fix.

Added regressions:

  • Root and standalone package now include referenced storage payloads with large protocol-4 frames plus malicious reduce payloads; they are included in pickle_files and reported.
  • Existing PiD-style raw tensor storage regression still stays out of pickle_files.

Final exact-head validation after this commit:

  • Focused live-review/PiD cases: 9 passed
  • Ruff format/check and mypy: clean
  • Broad lane: PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest -n auto -m "not slow and not integration" --maxfail=1 -> 17397 passed, 1292 skipped, 39 warnings
  • git diff --check HEAD~1 HEAD: clean
  • Exact-head pinned PiD QA on 853404d4: slice sha256 cc8ddcb749fa43a62b408e769376899c1c790c226b3aa41aa853abe76b333761; pickle_files only siglip2_decoder/data.pkl; direct codes S212, S510; aggregate exit 1, aggregate errors false.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 853404d4d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread modelaudit/scanners/pytorch_zip_scanner.py Outdated
@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

Follow-up for the latest P1 review is pushed at 4caf36100a20598020903127d196d2d57102b659.

Change:

  • Referenced storage members with a binary pickle protocol prefix now fail open to the full scanner on any truncated/incomplete probe, including the PROTO + BINBYTES(length > probe) single-opcode case.
  • Protocol-0-looking referenced storage remains complete-confirmed only, preserving the pinned PiD raw tensor-storage false-positive fix.

Pinned PiD selected-member QA on exact head 4caf36100a20598020903127d196d2d57102b659:

  • Source: nvidia/PiD@b4887b3c8fc65277a9b7a084292bf9fc0778c5ac, checkpoints/scale_rae/decoder/siglip2_sop14_i224_web73M_ganw3_decXL.pt, source size 1,662,529,538 bytes.
  • Bounded selected-only slice: siglip2_decoder/data.pkl, siglip2_decoder/version, siglip2_decoder/byteorder, siglip2_decoder/data/356; slice size 5,397,355, sha256 cc8ddcb749fa43a62b408e769376899c1c790c226b3aa41aa853abe76b333761.
  • Member siglip2_decoder/data/356: 5,308,416 bytes, sha256 23c4f4d8d8546f94994eebaf5a4dbe5b7eb963e69000f71ba491b673591d0f20.
  • Selected range fetch only: 13,817,669 bytes across 10 range requests; no unrelated model weights downloaded.
  • Scanner IDs: pytorch_zip; pickle_files: only siglip2_decoder/data.pkl.
  • Issue codes: S212, S510; no S901, no S902, and no issue/check mention for siglip2_decoder/data/356.
  • Scan outcome: inconclusive for missing unselected storage members and JIT snippet budget; success=true, has_errors=false; CLI exit 1 from findings, not scan errors.

Local validation on this head:

  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest tests/scanners/test_pytorch_zip_scanner.py::test_pytorch_zip_discovery_scans_referenced_storage_blob_when_it_is_a_pickle packages/modelaudit-picklescan/tests/test_api.py::test_scan_file_scans_referenced_storage_blob_when_it_is_a_pickle -> 8 passed.
  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest tests/scanners/test_pytorch_zip_scanner.py packages/modelaudit-picklescan/tests/test_api.py -k 'referenced_storage or hidden_storage_pickle or storage_reference or storage_blob or pinned_pid or unreferenced_storage or storage_lookalike or noncanonical_referenced_storage_aliases or torch_storage_untyped' -> 36 passed, 1466 deselected.
  • uv run ruff check --fix modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/ -> clean.
  • uv run ruff format --check modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/ -> clean.
  • uv run mypy modelaudit/ packages/modelaudit-picklescan/src packages/modelaudit-picklescan/tests tests/ -> clean.
  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest -n auto -m "not slow and not integration" --maxfail=1 -> 17399 passed, 1292 skipped, 39 warnings.
  • git diff --check -> clean.

Malicious controls covered by deterministic regression include protocol-0 storage pickle, normal binary pickle, large framed binary pickle, the new large length-prefixed binary prefix, unreferenced storage-lookalike members, noncanonical storage aliases, malformed storage references, and opcode-budget incomplete trust parsing.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

Final follow-up for the Windows CI failure is pushed at 3c4853d197e4eb7c2e4c89260b4662178131de5c.

Change:

  • The noncanonical ZIP member-name regression now writes the intentionally raw member name through ZipInfo so Windows does not normalize archive\\data\\0 to archive/data/0 before scanner discovery. This preserves the test's intended backslash-alias coverage on all platforms.

Pinned PiD selected-member QA on exact head 3c4853d197e4eb7c2e4c89260b4662178131de5c:

  • Source: nvidia/PiD@b4887b3c8fc65277a9b7a084292bf9fc0778c5ac, checkpoints/scale_rae/decoder/siglip2_sop14_i224_web73M_ganw3_decXL.pt, source size 1,662,529,538 bytes.
  • Selected-only slice: siglip2_decoder/data.pkl, siglip2_decoder/version, siglip2_decoder/byteorder, siglip2_decoder/data/356; slice size 5,397,355, sha256 cc8ddcb749fa43a62b408e769376899c1c790c226b3aa41aa853abe76b333761; selected range fetch only 13,817,669 bytes across 10 requests.
  • Member siglip2_decoder/data/356: 5,308,416 bytes, sha256 23c4f4d8d8546f94994eebaf5a4dbe5b7eb963e69000f71ba491b673591d0f20.
  • Scanner IDs: pytorch_zip; pickle_files: only siglip2_decoder/data.pkl.
  • Issue codes: S212, S510; no S901, no S902, and no issue/check mention for siglip2_decoder/data/356.
  • Scan outcome: inconclusive for missing unselected storage members and JIT snippet budget; success=true, has_errors=false; CLI exit 1 from findings, not scan errors.

Additional local validation on this head:

  • Windows-failing aliases: PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest tests/scanners/test_pytorch_zip_scanner.py::test_pytorch_zip_discovery_scans_noncanonical_referenced_storage_aliases packages/modelaudit-picklescan/tests/test_api.py::test_scan_file_scans_noncanonical_referenced_storage_aliases -> 4 passed.
  • Storage/PiD slice: PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest tests/scanners/test_pytorch_zip_scanner.py packages/modelaudit-picklescan/tests/test_api.py -k 'referenced_storage or hidden_storage_pickle or storage_reference or storage_blob or pinned_pid or unreferenced_storage or storage_lookalike or noncanonical_referenced_storage_aliases or torch_storage_untyped' -> 36 passed, 1466 deselected.
  • Ruff format/check, mypy -> clean.
  • Broad pre-commit lane after removing my ad hoc py_compile cache artifact: PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest -n auto -m "not slow and not integration" --maxfail=1 -> 17399 passed, 1292 skipped, 39 warnings.
  • git diff --check -> clean.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c4853d197

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py
Comment thread modelaudit/scanners/pytorch_zip_scanner.py
Comment thread packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py
@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

Follow-up for the latest Codex review threads is pushed at exact head 25ade41af1fb5949cce9cc9bfaf0d4c2ce4480cd.

Addressed review threads:

  • Proto-0/1 referenced storage prefixes that look like truncated text pickles now route to the full scanner unless the prefix contains a pickle FRAME, preserving the PiD raw-storage false-positive guard.
  • Root downgrade of storage PERSISTENT_ID warnings now uses the validated data.pkl trust map, not ZIP layout alone, so malformed/noncanonical storage PIDs remain suspicious.
  • Root and standalone storage-reference trust validation now share a cumulative 64 MiB data.pkl read budget; skipped validation fails open to normal member probing and records incomplete validation.

Pinned PiD selected-member QA on exact head 25ade41af1fb5949cce9cc9bfaf0d4c2ce4480cd:

  • Source: nvidia/PiD@b4887b3c8fc65277a9b7a084292bf9fc0778c5ac, checkpoints/scale_rae/decoder/siglip2_sop14_i224_web73M_ganw3_decXL.pt, source size 1,662,529,538 bytes.
  • Selected-only bounded slice: siglip2_decoder/data.pkl, siglip2_decoder/version, siglip2_decoder/byteorder, siglip2_decoder/data/356; slice size 5,397,355, sha256 cc8ddcb749fa43a62b408e769376899c1c790c226b3aa41aa853abe76b333761; range fetch only 13,817,669 bytes across 10 requests.
  • Member hashes: data.pkl 8ee22aac231310f794cd58537e4d6d19f08ab32a99a573d18e0f0d2fcfa57bd8 (88,413 bytes); data/356 23c4f4d8d8546f94994eebaf5a4dbe5b7eb963e69000f71ba491b673591d0f20 (5,308,416 bytes).
  • Before, on main 8d6c4864fe2ea833ceaef1b9803d225afb1e8d69: scanner pytorch_zip; pickle_files included siglip2_decoder/data.pkl and siglip2_decoder/data/356; codes S212, S510, S901, S902; data/356 emitted S902 FRAME-overrun plus S901 parse-incomplete; CLI exit 1, scan errors 0.
  • After, on this exact head: scanner pytorch_zip; pickle_files only siglip2_decoder/data.pkl; issue codes S212, S510 plus expected missing-storage inconclusive info; no S901, no S902, no issue/check mention for siglip2_decoder/data/356; success=true, has_errors=false; CLI exit 1 from findings, not scan errors.

Malicious/control coverage preserved or strengthened:

  • Referenced malicious storage pickles, including protocol-0/text pickle with STOP after the 4 KiB probe, binary pickle opcodes, large framed payloads, and length-prefixed/truncated binary prefixes route to full scan.
  • Hidden or unreferenced malicious pickle members, malformed storage references, noncanonical/backslash/leading-slash aliases, normalized-name collisions, renamed/disguised payloads, archive polyglots, and incomplete storage-reference analysis remain detected or fail closed/inconclusive.
  • The cumulative trust-validation budget test proves skipped validation does not suppress later malicious storage blobs.

Local validation on this exact head:

  • Targeted review regressions: 13 passed.
  • Storage/PiD slice: 41 passed, 1466 deselected.
  • Ruff format/check and mypy: clean.
  • Broad lane: PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest -n auto -m "not slow and not integration" --maxfail=1 -> 17404 passed, 1292 skipped, 39 warnings.
  • git diff --check -> clean.

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 25ade41af1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/modelaudit-picklescan/src/modelaudit_picklescan/api.py
@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

Validated-storage sidecar follow-up is pushed at exact head c34feb5496c607bd40b01339e8c59ca858406122.

Addressed latest Codex review:

  • _detect_suspicious_files now receives the validated data.pkl -> storage keys map computed before discovery and uses it directly to decide which data/<n> blobs are referenced tensor storage.
  • This prevents later validated storage members from being treated as executable sidecars just because the pickle scanner summarized PERSISTENT_ID metadata after the first entry.
  • Added a regression with two validated storage blobs where data/1 starts with ELF bytes; it remains trusted raw tensor storage. Existing unreferenced/untrusted executable sidecar controls still detect.

Pinned PiD selected-member QA on exact head c34feb5496c607bd40b01339e8c59ca858406122:

  • Source: nvidia/PiD@b4887b3c8fc65277a9b7a084292bf9fc0778c5ac, checkpoints/scale_rae/decoder/siglip2_sop14_i224_web73M_ganw3_decXL.pt, source size 1,662,529,538 bytes.
  • Selected-only bounded slice: siglip2_decoder/data.pkl, siglip2_decoder/version, siglip2_decoder/byteorder, siglip2_decoder/data/356; slice size 5,397,355, sha256 cc8ddcb749fa43a62b408e769376899c1c790c226b3aa41aa853abe76b333761; selected range fetch only 13,817,669 bytes across 10 requests.
  • Member hashes: data.pkl 8ee22aac231310f794cd58537e4d6d19f08ab32a99a573d18e0f0d2fcfa57bd8 (88,413 bytes); data/356 23c4f4d8d8546f94994eebaf5a4dbe5b7eb963e69000f71ba491b673591d0f20 (5,308,416 bytes).
  • Before, on main 8d6c4864fe2ea833ceaef1b9803d225afb1e8d69: scanner pytorch_zip; pickle_files included siglip2_decoder/data.pkl and siglip2_decoder/data/356; codes S212, S510, S901, S902; data/356 emitted S902 FRAME-overrun plus S901 parse-incomplete; CLI exit 1, scan errors 0.
  • After, on this exact head: scanner pytorch_zip; pickle_files only siglip2_decoder/data.pkl; issue codes S212, S510 plus expected missing-storage/JIT inconclusive info; no S901, no S902, no issue/check mention for siglip2_decoder/data/356; success=true, has_errors=false; CLI exit 1 from findings, not scan errors.

Local validation on this exact worktree:

  • Executable-sidecar focused set: 4 passed.
  • Non-network storage/executable slice: 50 passed, 1464 deselected.
  • Ruff format/check and mypy: clean.
  • Pinned QA JSON check: no data/356, S901, or S902 mentions.
  • git diff --check: clean.
  • The network-backed slow pinned test was attempted in a broader slice and hit an HF CDN SSLEOFError; the same selected member was validated from the already materialized bounded local slice above.
  • Broad xdist lane note from the preceding exact worktree: attempted twice and stopped on the same unrelated cache test (tests/cache/test_cache_correctness.py::test_cached_scan_does_not_serialize_known_uncacheable_scan_result, release_calls == 2 vs 1); that test passed in isolation.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

Windows alias CI follow-up is pushed at exact head ab2137e7b8cc01405f9c97c858a10187451581dd.

Change:

  • The noncanonical backslash storage-alias tests now accept the scanner-reported member name when Windows/zipfile reports archive\\data\\0 as archive/data/0, while still requiring the malicious payload to be scanned and reported.
  • The leading-slash alias behavior is unchanged: it remains rejected as an unsafe archive path and is not trusted as tensor storage.

Pinned PiD selected-member QA on exact head ab2137e7b8cc01405f9c97c858a10187451581dd:

  • Source: nvidia/PiD@b4887b3c8fc65277a9b7a084292bf9fc0778c5ac, checkpoints/scale_rae/decoder/siglip2_sop14_i224_web73M_ganw3_decXL.pt, source size 1,662,529,538 bytes.
  • Selected-only bounded slice: siglip2_decoder/data.pkl, siglip2_decoder/version, siglip2_decoder/byteorder, siglip2_decoder/data/356; slice size 5,397,355, sha256 cc8ddcb749fa43a62b408e769376899c1c790c226b3aa41aa853abe76b333761; selected range fetch only 13,817,669 bytes across 10 requests.
  • Member hashes: data.pkl 8ee22aac231310f794cd58537e4d6d19f08ab32a99a573d18e0f0d2fcfa57bd8 (88,413 bytes); data/356 23c4f4d8d8546f94994eebaf5a4dbe5b7eb963e69000f71ba491b673591d0f20 (5,308,416 bytes).
  • Before, on main 8d6c4864fe2ea833ceaef1b9803d225afb1e8d69: scanner pytorch_zip; pickle_files included siglip2_decoder/data.pkl and siglip2_decoder/data/356; codes S212, S510, S901, S902; data/356 emitted S902 FRAME-overrun plus S901 parse-incomplete; CLI exit 1, scan errors 0.
  • After, on this exact head: scanner pytorch_zip; pickle_files only siglip2_decoder/data.pkl; issue codes S212, S510 plus expected missing-storage/JIT inconclusive info; no S901, no S902, no issue/check mention for siglip2_decoder/data/356; success=true, has_errors=false; CLI exit 1 from findings, not scan errors.

Local validation on this exact worktree:

  • Windows-failing alias tests: 4 passed.
  • Non-network storage/executable slice: 50 passed, 1464 deselected.
  • Ruff format/check and mypy: clean.
  • Pinned QA JSON check: no data/356, S901, or S902 mentions.
  • git diff --check: clean.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mldangelo-oai mldangelo-oai enabled auto-merge (squash) June 11, 2026 10:23
@mldangelo-oai mldangelo-oai disabled auto-merge June 11, 2026 12:47
@mldangelo-oai

Copy link
Copy Markdown
Contributor Author

Exact-head independent review of ab2137e7b8cc01405f9c97c858a10187451581dd is blocked despite fully green CI and zero unresolved review threads.

P1: loadable protocol-less framed pickle bypasses both scanners

The trusted-storage probe returns false when a protocol-less pickle contains FRAME and STOP is beyond the 4 KiB probe. A valid payload using builtins.eval, a frame, and a valid BINPERSID for torch.ByteStorage was executable with both pickle.loads and torch.load(..., weights_only=False). Base routed archive/data/0 and emitted critical S104/S201 findings. Head omitted archive/data/0 in both the main and standalone scanners, leaving only the unrelated persistent-ID warning. Security-relevant opcodes observed before an incomplete frame must force routing rather than treating the frame as trusted tensor evidence.

P2: the new trust pass ignores configured timeout and aggregate work budgets

An archive with 64 prefixes, each containing version, data/0, and a 100,001-byte data.pkl with 100,000 opcodes, took 34.3 seconds in the main scanner and 13.8 seconds in standalone with a one-second timeout. The per-member opcode limit does not share an aggregate budget or check the deadline. Thread the deadline through discovery and enforce aggregate opcode/work bounds.

P3: three changed persistent-ID tests fail in the standard macOS all-CI environment

Installed Torch call-graph enrichment promotes the expected SUSPICIOUS verdict to MALICIOUS; the tests pass only in the isolated standalone environment. Isolate source-sensitive enrichment or assert the intended platform-independent signal.

Validation: focused main storage suite 30 passed; Rust 164 passed; duplicate-data controls 3 passed; pinned nvidia/PiD@b4887b3c... bounded HF test passed; real torch.save protocols 1-5 loaded successfully; malformed, truncated, oversized, executable, and nested controls failed closed except for the P1 case. Please address P1/P2, stabilize the P3 tests, and re-request exact-head review.

…t26-pytorch-storage-member-discovery-20260610
Fail closed for referenced storage members that contain framed pickle structure beyond the trusted-storage probe, while preserving raw tensor false-positive suppression. Add root and standalone regressions for delayed dangerous opcodes and aggregate trust-parser budgets.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

and details.get("opcode") == "BINPERSID"

P2 Badge Downgrade trusted protocol-0 storage IDs

When data.pkl is saved with pickle_protocol=0, the new trust parser accepts the PERSID storage reference and trusts the referenced storage blob, but this predicate only downgrades BINPERSID reports. The subsequent pickle scan therefore still leaves a PERSISTENT_ID warning for a validated PyTorch storage lookup, so benign legacy PyTorch ZIP archives saved with protocol 0 continue to surface as suspicious even though their storage member was trusted.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…t26-pytorch-storage-member-discovery-20260610
Separate raw tensor storage trust from persistent-ID downgrade trust so mixed persistent-ID payloads remain suspicious. Cover root and standalone protocol-0 storage-only, arbitrary, noncanonical, and storage-first mixed cases.
…t26-pytorch-storage-member-discovery-20260610
Annotate canonical legacy PyTorch storage BINPERSID findings from the validated object-stream parser so trusted legacy containers downgrade correctly while bare .bin storage PIDs remain suspicious with structured metadata.
…t26-pytorch-storage-member-discovery-20260610

# Conflicts:
#	modelaudit/scanners/pytorch_zip_scanner.py
#	tests/scanners/test_pytorch_zip_scanner.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 617827abcd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2829 to +2830
trusted_storage_blob_members = self._storage_blob_members_from_data_pkl_members(
trusted_pytorch_storage_data_pkl_members

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate executable skips on fully trusted storage

With pickle scanning disabled, this broad trust set skips executable probing for data/<n> even when data.pkl also has an arbitrary PERSID. That can hide ELF bytes in data/0 and leave the PyTorch-only scan clean; gate this skip on the fully validated storage set. guidance

Useful? React with 👍 / 👎.

…t26-pytorch-storage-member-discovery-20260610
…t26-pytorch-storage-member-discovery-20260610

# Conflicts:
#	packages/modelaudit-picklescan/rust/src/stack.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0dd6a452d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread modelaudit/scanners/pytorch_zip_scanner.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant