Skip to content

fix: fail closed on incomplete XGBoost analysis#1019

Merged
mldangelo-oai merged 2 commits intomainfrom
mdangelo/codex/fail-closed-xgboost-parsing
Apr 16, 2026
Merged

fix: fail closed on incomplete XGBoost analysis#1019
mldangelo-oai merged 2 commits intomainfrom
mdangelo/codex/fail-closed-xgboost-parsing

Conversation

@mldangelo-oai
Copy link
Copy Markdown
Contributor

Summary

Fail closed when XGBoost analysis cannot complete because the model is malformed, the UBJ decoder is unavailable, or a .bst/.model file is actually a pickle.

Security impact

Previously, several XGBoost scanner paths emitted informational findings but still finished successfully: malformed JSON, missing UBJSON support, and pickle spoofing on binary model extensions. That let these evasions exit cleanly even though analysis was incomplete. This change adds explicit inconclusive metadata for those cases and finishes the scan as unsuccessful so the gate does not pass them as clean models.

Validation

  • uv run ruff format modelaudit/scanners/xgboost_scanner.py tests/scanners/test_xgboost_scanner.py
  • PROMPTFOO_DISABLE_TELEMETRY=1 uv run pytest tests/scanners/test_xgboost_scanner.py -q
  • uv run mypy modelaudit/scanners/xgboost_scanner.py tests/scanners/test_xgboost_scanner.py

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 15, 2026

Workflow run and artifacts

Performance Benchmarks

Compared 19 shared benchmarks with a regression threshold of 15%.
Status: 0 regressions, 0 improved, 19 stable, 0 new, 0 missing.
Aggregate shared-benchmark median: 142.27ms -> 142.23ms (-0.0%).

Benchmark Target Size Files Baseline Current Change Status
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_dangerous_global_payloads[malicious_reduce] malicious_reduce 52 B 1 46.7us 50.4us +7.8% stable
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_nested_payloads[nested_raw] nested_raw 78 B 1 61.4us 65.7us +7.0% stable
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_dangerous_global_payloads[stack_global] stack_global 21 B 1 39.7us 42.1us +5.9% stable
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_nested_payloads[nested_hex] nested_hex 130 B 1 67.1us 69.2us +3.1% stable
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_safe_payloads[safe_small] safe_small 68 B 1 35.4us 36.5us +2.9% stable
tests/benchmarks/test_scan_benchmarks.py::test_skip_filter_plain_text_files - 4.6 KiB 256 7.99ms 7.77ms -2.7% stable
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_opcode_budget_tail_payload opcode_budget_tail 14 B 1 45.0us 46.0us +2.2% stable
tests/benchmarks/test_scan_benchmarks.py::test_validate_file_type_pytorch_zip state_dict.pt 1.5 MiB 1 34.7us 34.0us -2.1% stable
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_multi_stream_padded_payload multi_stream_padded 4.1 KiB 1 86.3us 87.9us +1.8% stable
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_hidden_suspicious_string_budget hidden_suspicious_string 8.0 KiB 1 448.3us 455.2us +1.5% stable
tests/benchmarks/test_scan_benchmarks.py::test_scan_safe_pickle safe_model.pkl 49.4 KiB 1 8.40ms 8.52ms +1.3% stable
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_nested_payloads[nested_base64] nested_base64 98 B 1 64.5us 65.3us +1.2% stable
tests/benchmarks/test_scan_benchmarks.py::test_detect_file_format_safe_pickle safe_model.pkl 49.4 KiB 1 19.3us 19.1us -1.0% stable
tests/benchmarks/test_scan_benchmarks.py::test_scan_pytorch_zip state_dict.pt 1.5 MiB 1 24.24ms 24.45ms +0.9% stable
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_safe_payloads[long_benign_string] long_benign_string 1.0 MiB 1 907.2us 912.0us +0.5% stable
tests/benchmarks/test_scan_benchmarks.py::test_scan_mixed_directory mixed-corpus 1.7 MiB 54 56.93ms 56.66ms -0.5% stable
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_chunked_stream chunked_stream 278.2 KiB 1 5.21ms 5.23ms +0.4% stable
tests/benchmarks/test_scan_benchmarks.py::test_scan_duplicate_directory duplicate-corpus 840.0 KiB 81 34.75ms 34.83ms +0.2% stable
tests/benchmarks/test_picklescan_benchmarks.py::test_picklescan_safe_payloads[safe_large] safe_large 278.2 KiB 1 2.89ms 2.89ms -0.1% stable

@mldangelo-oai mldangelo-oai changed the title [codex] fix: fail closed on incomplete XGBoost analysis fix: fail closed on incomplete XGBoost analysis Apr 15, 2026
@mldangelo-oai mldangelo-oai marked this pull request as ready for review April 15, 2026 23:31
@mldangelo-oai mldangelo-oai force-pushed the mdangelo/codex/fail-closed-xgboost-parsing branch from 430aa83 to 446011e Compare April 16, 2026 06:44
@mldangelo-oai mldangelo-oai merged commit b8f334e into main Apr 16, 2026
8 checks passed
@mldangelo-oai mldangelo-oai deleted the mdangelo/codex/fail-closed-xgboost-parsing branch April 16, 2026 06:44
@github-actions github-actions bot mentioned this pull request Apr 16, 2026
Copy link
Copy Markdown

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

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: 446011e676

ℹ️ 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/core.py
HEADER_FORMAT_TO_SCANNER_ID = _registry.get_header_format_to_scanner_ids()
_COMPRESSED_HEADER_FORMATS = frozenset({"compressed", "gzip", "bzip2", "xz", "lz4", "zlib"})
_R_SERIALIZED_EXTENSIONS = frozenset({".rds", ".rda", ".rdata"})
_XGBOOST_BINARY_EXTENSIONS = frozenset({".bst"})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Include .model in XGBoost pickle-spoof fail-closed gate

_scan_file_internal() computes is_xgboost_pickle_spoof from _XGBOOST_BINARY_EXTENSIONS, but that set only contains .bst. .model is still treated as an XGBoost binary extension elsewhere, so a pickle renamed to .model bypasses the new spoof fail-closed path and is handled as a normal pickle scan instead of being marked inconclusive.

Useful? React with 👍 / 👎.

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