From c6ef38bc3b7b48825cb7c2d78e0604b168164a70 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 28 Sep 2026 19:12:12 +0900 Subject: [PATCH 1/7] fix(strix): request changed-file evidence in PR reports --- scripts/ci/strix_quick_gate.sh | 7 +++++++ scripts/ci/test_strix_quick_gate.sh | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index ccf08f48eb..aa60e8c766 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -2679,6 +2679,10 @@ run_strix_once() { local resolved_target_path local timeout_seconds="$STRIX_PROCESS_TIMEOUT_SECONDS" local total_budget_limited_timeout=0 + local report_scope_instruction="" + if is_pull_request_event; then + report_scope_instruction="For this pull request scan, name at least one repository-relative changed file that you actually inspected in the final report. State what you checked. If no changed file was inspected, say so and do not claim a completed source review. Report vulnerabilities normally." + fi if [ "$RUN_START_EPOCH" -le 0 ]; then RUN_START_EPOCH="$(date +%s)" fi @@ -2740,6 +2744,7 @@ run_strix_once() { STRIX_CHILD_EXECUTABLE_ROOT="$STRIX_EXECUTABLE_ROOT" \ STRIX_CHILD_EXECUTABLE_SHA256="$STRIX_EXECUTABLE_SHA256" \ STRIX_CHILD_REQUIRE_EXECUTABLE_INTEGRITY="${IS_PR_EVIDENCE_RUN:-false}" \ + STRIX_CHILD_REPORT_SCOPE_INSTRUCTION="$report_scope_instruction" \ python3 - "$timeout_seconds" "$resolved_target_path" "$SCAN_MODE" "$STRIX_LOG" "$STRIX_SCAN_WORKING_DIR" <<'PY' import hashlib import hmac @@ -2914,6 +2919,8 @@ scan_output_dir.mkdir() # scan target. The target remains explicit and absolute, so changing cwd cannot # change which source tree is scanned. command = [resolved_strix_bin, "-n", "-t", str(target_cwd), "--scan-mode", scan_mode] +if os.environ.get("STRIX_CHILD_REPORT_SCOPE_INSTRUCTION"): + command.extend(["--instruction", os.environ["STRIX_CHILD_REPORT_SCOPE_INSTRUCTION"]]) try: process = subprocess.Popen( diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 80c4832243..51dfa45f9b 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -7061,6 +7061,10 @@ while [ "$#" -gt 0 ]; do done scoped_file="$target_path/${FAKE_STRIX_EXPECTED_CHANGED_FILE:?}" +if [[ " $* " != *" --instruction For this pull request scan, name at least one repository-relative changed file that you actually inspected"* ]]; then + echo "Error: PR scan did not request source-specific report evidence" >&2 + exit 69 +fi if [ ! -f "$scoped_file" ]; then echo "Error: PR head scoped file missing ($scoped_file)" >&2 exit 61 From d3d1dedc97f46a07ffe15414b3ac89ccfaefd7af Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 28 Sep 2026 19:46:51 +0900 Subject: [PATCH 2/7] fix(strix): reject copied finish-tool placeholder reports --- scripts/ci/strix_report_scope.py | 14 ++++++++++++++ tests/test_strix_report_scope.py | 25 ++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/scripts/ci/strix_report_scope.py b/scripts/ci/strix_report_scope.py index 44de1c441d..c8f538db20 100644 --- a/scripts/ci/strix_report_scope.py +++ b/scripts/ci/strix_report_scope.py @@ -8,6 +8,14 @@ from pathlib import Path +FINISH_TOOL_PLACEHOLDERS = { + "executive_summary": "Business-level summary for leadership.", + "methodology": "Frameworks, scope, and approach.", + "technical_analysis": "Consolidated findings + systemic themes.", + "recommendations": "Prioritized, actionable remediation.", +} + + def validate(output: Path, changed_paths: list[str]) -> None: if not output.is_dir() or output.is_symlink(): raise ValueError("scan output directory is missing") @@ -27,6 +35,12 @@ def validate(output: Path, changed_paths: list[str]) -> None: raise ValueError("scan results are not an object") if metadata.get("status") != "completed" or results.get("scan_completed") is not True or results.get("success") is not True: raise ValueError("scan report is incomplete") + for field, placeholder in FINISH_TOOL_PLACEHOLDERS.items(): + value = results.get(field) + if not isinstance(value, str) or not value.strip(): + raise ValueError("scan report is incomplete") + if value.strip() == placeholder: + raise ValueError("scan report contains a finish-tool placeholder") report = report_path.read_text(encoding="utf-8") if not any(path in report for path in changed_paths): raise ValueError("scan report does not identify a changed source file") diff --git a/tests/test_strix_report_scope.py b/tests/test_strix_report_scope.py index 3ff7964659..443ea3d488 100644 --- a/tests/test_strix_report_scope.py +++ b/tests/test_strix_report_scope.py @@ -13,7 +13,19 @@ def test_report_scope_rejects_unrelated_success_and_accepts_scoped_success(tmp_p run = tmp_path / "current-scan" run.mkdir() (run / "run.json").write_text( - json.dumps({"status": "completed", "scan_results": {"scan_completed": True, "success": True}}), + json.dumps( + { + "status": "completed", + "scan_results": { + "scan_completed": True, + "success": True, + "executive_summary": "No issues found in the changed file.", + "methodology": "Reviewed the changed source file.", + "technical_analysis": "The changed function preserves authorization checks.", + "recommendations": "Retain the existing checks.", + }, + } + ), encoding="utf-8", ) report = run / "penetration_test_report.md" @@ -24,3 +36,14 @@ def test_report_scope_rejects_unrelated_success_and_accepts_scoped_success(tmp_p report.write_text("Assessed python/fast_mlsirm/report.py; no vulnerabilities found.\n", encoding="utf-8") assert subprocess.run(command, capture_output=True).returncode == 0 assert subprocess.run(command[:-1], capture_output=True).returncode == 1 + metadata = json.loads((run / "run.json").read_text(encoding="utf-8")) + metadata["scan_results"]["executive_summary"] = "Business-level summary for leadership." + (run / "run.json").write_text(json.dumps(metadata), encoding="utf-8") + result = subprocess.run(command, capture_output=True, text=True, check=False) + assert result.returncode == 1 + assert "placeholder" in result.stderr + del metadata["scan_results"]["executive_summary"] + (run / "run.json").write_text(json.dumps(metadata), encoding="utf-8") + result = subprocess.run(command, capture_output=True, text=True, check=False) + assert result.returncode == 1 + assert "incomplete" in result.stderr From be831a33dac68015f604d2be9c5e6fd9f2404211 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 28 Sep 2026 19:53:55 +0900 Subject: [PATCH 3/7] fix(strix): forbid copied finish-tool prose in PR scans --- scripts/ci/strix_quick_gate.sh | 2 +- scripts/ci/test_strix_quick_gate.sh | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index aa60e8c766..e08bd3a788 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -2681,7 +2681,7 @@ run_strix_once() { local total_budget_limited_timeout=0 local report_scope_instruction="" if is_pull_request_event; then - report_scope_instruction="For this pull request scan, name at least one repository-relative changed file that you actually inspected in the final report. State what you checked. If no changed file was inspected, say so and do not claim a completed source review. Report vulnerabilities normally." + report_scope_instruction="For this pull request scan, name at least one repository-relative changed file that you actually inspected in the final report. State what you checked. Do not copy finish_scan parameter descriptions into the report. If no changed file was inspected, say so and do not claim a completed source review. Report vulnerabilities normally." fi if [ "$RUN_START_EPOCH" -le 0 ]; then RUN_START_EPOCH="$(date +%s)" diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 51dfa45f9b..3ccfd49d03 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -7065,6 +7065,10 @@ if [[ " $* " != *" --instruction For this pull request scan, name at least one r echo "Error: PR scan did not request source-specific report evidence" >&2 exit 69 fi +if [[ " $* " != *"Do not copy finish_scan parameter descriptions into the report"* ]]; then + echo "Error: PR scan did not reject finish-tool example prose" >&2 + exit 70 +fi if [ ! -f "$scoped_file" ]; then echo "Error: PR head scoped file missing ($scoped_file)" >&2 exit 61 From cd84d887223aa61c8ab30cfcddaaee6d8f8f28c7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 30 Sep 2026 13:29:00 +0900 Subject: [PATCH 4/7] test(security): require patched Strix and Rust fixture locks --- tests/test_rust_coverage_fixture_dependencies.py | 16 ++++++++++++++++ tests/test_strix_runtime_dependencies.py | 13 +++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/test_rust_coverage_fixture_dependencies.py diff --git a/tests/test_rust_coverage_fixture_dependencies.py b/tests/test_rust_coverage_fixture_dependencies.py new file mode 100644 index 0000000000..013bf77f33 --- /dev/null +++ b/tests/test_rust_coverage_fixture_dependencies.py @@ -0,0 +1,16 @@ +"""Security contracts for the offline Rust coverage dependency fixture.""" + +from pathlib import Path + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +FIXTURE_ROOT = REPOSITORY_ROOT / "tests" / "fixtures" / "coverage-cargo" + + +def test_coverage_fixture_uses_patched_pyo3_release() -> None: + """Keep the fixture manifest and lock on the reviewed PyO3 0.29.2 release.""" + manifest = (FIXTURE_ROOT / "Cargo.toml").read_text(encoding="utf-8") + lock = (FIXTURE_ROOT / "Cargo.lock").read_text(encoding="utf-8") + + assert 'pyo3 = { version = "=0.29.2"' in manifest + assert 'name = "pyo3"\nversion = "0.29.2"' in lock diff --git a/tests/test_strix_runtime_dependencies.py b/tests/test_strix_runtime_dependencies.py index 20444b0abd..755ceab855 100644 --- a/tests/test_strix_runtime_dependencies.py +++ b/tests/test_strix_runtime_dependencies.py @@ -27,3 +27,16 @@ def test_strix_anyio_security_pin_is_an_explicit_lock_input() -> None: assert "anyio==4.14.2" in requirements.splitlines() assert "anyio==4.14.2 \\" in requirements_lock.splitlines() + + +def test_strix_pyjwt_security_pin_is_an_explicit_lock_input() -> None: + """Keep the patched PyJWT version reproducible from the source input.""" + requirements = (REPOSITORY_ROOT / "requirements-strix-ci.txt").read_text( + encoding="utf-8" + ) + requirements_lock = ( + REPOSITORY_ROOT / "requirements-strix-ci-hashes.txt" + ).read_text(encoding="utf-8") + + assert "pyjwt==2.14.0" in requirements.splitlines() + assert "pyjwt==2.14.0 \\" in requirements_lock.splitlines() From 1ca4b94f51c06156af663299da83470c3f54ae09 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 30 Sep 2026 13:29:34 +0900 Subject: [PATCH 5/7] fix(security): refresh shared PyJWT and PyO3 locks --- CHANGELOG.md | 11 +++ ...d-security-baseline-pyjwt-pyo3-20260930.md | 99 +++++++++++++++++++ docs/product-technical-gap-baseline.md | 10 ++ requirements-strix-ci-hashes.txt | 11 ++- requirements-strix-ci.txt | 1 + tests/fixtures/coverage-cargo/Cargo.lock | 72 +++----------- tests/fixtures/coverage-cargo/Cargo.toml | 2 +- 7 files changed, 141 insertions(+), 65 deletions(-) create mode 100644 docs/doctoring/shared-security-baseline-pyjwt-pyo3-20260930.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d90fa0c899..0a112f74bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +### Shared security fixtures use patched PyJWT and PyO3 releases + +- The Strix hash lock now takes PyJWT `2.14.0` as an explicit source input, + closing CVE-2026-102274 without hiding the dependency in the cryptography-only + override file. The offline Rust coverage fixture advances from PyO3 `0.22.6` + to `0.29.2`, beyond the `0.29.0` fixes for GHSA-36hh-v3qg-5jq4 and + GHSA-chgr-c6px-7xpp. Source/lock parity tests prevent either generated lock + from silently returning to the vulnerable versions. Protected integration, + immutable consumer-pin advancement, and fresh exact-head hosted security + Checks remain required before release admission. + ### Intel macOS native archives are bound to x86_64 bytes - The release prescreener now requires every native member in an Intel macOS diff --git a/docs/doctoring/shared-security-baseline-pyjwt-pyo3-20260930.md b/docs/doctoring/shared-security-baseline-pyjwt-pyo3-20260930.md new file mode 100644 index 0000000000..0b1eb5a725 --- /dev/null +++ b/docs/doctoring/shared-security-baseline-pyjwt-pyo3-20260930.md @@ -0,0 +1,99 @@ +# Shared PyJWT and PyO3 security baseline repair + +## Status and decision + +**Proposed; release HOLD.** The central `.github` repository owns both affected +dependency surfaces, so the repair belongs on a protected-main foundation PR. +It must not be copied into PR #1026, which changes neither lock. The owner repair +must pass exact-head review and hosted security Checks, merge ordinarily, and +then reach #1026 through a non-force merge from protected `main`. + +## Exact incident evidence + +PR [#1026](https://github.com/ContextualWisdomLab/.github/pull/1026) was observed +at exact head `6f645a73502e159d5a229805afa34868ad9bb851` against protected +`main@37b10243cec3d160ecc9c1be75c71428b160a703`. + +- [Security Scan run 36495499815](https://github.com/ContextualWisdomLab/.github/actions/runs/36495499815), + job `109380628690`, reported PyO3 `0.22.6` in + `tests/fixtures/coverage-cargo/Cargo.lock`: GHSA-36hh-v3qg-5jq4 (High, 8.0) + and GHSA-chgr-c6px-7xpp (Medium, 5.5). Both advisories fix the defect in + PyO3 `0.29.0`; this repair selects the already reviewed `0.29.2` release. +- [Python Security run 36495499871](https://github.com/ContextualWisdomLab/.github/actions/runs/36495499871), + job `109380725819`, reported PyJWT `2.13.0` in + `requirements-strix-ci-hashes.txt` as affected by CVE-2026-102274. PyJWT + `2.14.0` is the fixed release. +- The exact #1026 diff changes neither vulnerable file. The same lock bytes + were present on protected `main`, establishing a shared baseline defect rather + than a PR-specific regression. + +## Root cause and operational scenarios + +PyJWT was only a transitive MCP dependency, so the generated Strix lock could +select a newly vulnerable release without an explicit reviewed source pin. A +malformed RSA JWK can raise a plain `ValueError` and abort processing of the +whole JWK Set. An operator can therefore lose otherwise valid signing keys and +fail authentication or review-agent startup because one untrusted key is bad. + +The offline Rust coverage fixture intentionally pins exact crate releases, but +its PyO3 pin was not advanced when the two 2026 advisories were published. One +defect permits an out-of-bounds read from iterator methods; the other omits a +required `Sync` bound and permits a data race. Even though this is a test +fixture, the central scanner correctly treats its lock as executable supply +chain material. + +## RED to GREEN contract + +RED commit `cd84d887` introduced two fail-closed contracts: + +1. `requirements-strix-ci.txt` must explicitly select PyJWT `2.14.0`, and the + generated hash lock must contain the same version. +2. The Rust coverage fixture manifest and lock must both select PyO3 `0.29.2`. + +The implementation adds the direct PyJWT input, regenerates the Python 3.13 +manylinux hash lock with the repository command, advances the exact PyO3 +manifest pin, and regenerates the Cargo lock with Rust `1.97.1`. The +cryptography override remains single-purpose; it does not become a general +security-version overlay. + +## Ownership, release, and failure recovery + +The fixed-source release workflows consume `requirements-strix-ci-hashes.txt` +from immutable central revisions. This PR repairs the canonical owner bytes but +does not rewrite those workflows to an open branch. After ordinary protected +merge, a separate consumer change must advance their exact source commit and +rerun API/schema, security, SBOM, and provenance evidence. If any exact-head +scanner, build, or independent review fails, the PR remains HOLD and the root +cause is repaired here; no bypass or mutable source reference is permitted. + +## Local verification on the repaired tree + +- Focused dependency, fixed-source, Maturin asset, and Rust toolchain contracts: + 48 passed, 1 skipped. +- Repository regression suite: 5,160 passed, 11 skipped, 40 subtests passed. +- Rust `1.97.1` `cargo check --locked`: passed for the coverage fixture. +- Python lock regeneration from the existing reviewed lock: byte-identical; + hash-enforced installation loaded PyJWT `2.14.0`. +- `pip-audit`: no known vulnerabilities in the Strix lock. OSV's direct + `pyo3@0.29.2` query returned no vulnerability records. + +No production Python module changes in this repair. The repository-wide +coverage and docstring commands expose separate protected-main baseline debt: +coverage is 99% (178 statements missing) and `interrogate scripts/ci` is 97% +(43 docstrings missing). Those failures are not waived or called green here; +they require their own bounded owner repair before the repository can claim the +100% global gates. + +## References + +GitHub. (2026, June 12). *Out-of-bounds read in PyO3 iterator methods* +(GHSA-36hh-v3qg-5jq4). GitHub Advisory Database. +https://github.com/advisories/GHSA-36hh-v3qg-5jq4 + +GitHub. (2026, June 12). *PyO3 missing Sync bound can lead to a data race* +(GHSA-chgr-c6px-7xpp). GitHub Advisory Database. +https://github.com/advisories/GHSA-chgr-c6px-7xpp + +Open Source Vulnerabilities. (2026). *CVE-2026-102274: PyJWT RSA JWK Set +availability failure*. +https://osv.dev/vulnerability/CVE-2026-102274 diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 6e5f1c549a..0c477915d8 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -7,6 +7,16 @@ 이 문서는 제품·기술·운영 Gap을 현재 문서와 현재 GitHub 상태에 묶어 두는 기준선이다. 새 작업은 먼저 이 문서의 Gap ID를 PR 설명과 테스트 증거에 연결하고, PR의 정확한 exact HEAD·Checks·리뷰를 다시 수집한 뒤 구현한다. 표의 상태는 작성 시점의 관측값이므로, 병합 판단에는 재사용하지 않는다. 이 인벤토리는 스냅샷이며 merge authorization이 아니다. +### 2026-09-30 공유 보안 기준 exact-head delta + +이 delta는 아래 2026-08-26 인벤토리를 덮어쓰지 않는다. 2026-09-30 재수집한 +보호 `main`은 `37b10243cec3d160ecc9c1be75c71428b160a703`이고, live API의 첫 +페이지에는 열린 PR 50개가 있었다. 페이지 전체를 조직의 총 PR 수로 추론하지 않는다. + +| Gap ID | 상태 | exact-head evidence | causal owner / next gate | +|---|---|---|---| +| CONTROL-SHARED-SECURITY-LOCK-01 | **Source repair in progress — release HOLD** | `.github#1026@6f645a73502e159d5a229805afa34868ad9bb851`의 Security Scan run `36495499815`는 공통 Rust fixture의 PyO3 `0.22.6`에서 GHSA-36hh-v3qg-5jq4와 GHSA-chgr-c6px-7xpp를 검출했고, Python Security run `36495499871`은 공통 Strix hash lock의 PyJWT `2.13.0`에서 CVE-2026-102274를 검출했다. 두 파일은 #1026 변경 범위 밖이며 보호 `main`에도 동일하게 남아 있었다. RED commit `cd84d887`는 PyO3 `0.29.2`와 PyJWT `2.14.0` source/lock parity를 요구한다. | 중앙 `.github`가 공통 fixture와 Strix lock을 소유한다. [RCA와 검증 계약](doctoring/shared-security-baseline-pyjwt-pyo3-20260930.md)에 따라 owner PR의 exact-head Checks와 독립 승인, ordinary protected merge, immutable consumer source pin 갱신, 그리고 #1026의 비강제 main merge-forward가 순서대로 필요하다. 어떤 실패도 #1026 전용 패치나 bypass로 처리하지 않는다. | + ### 2026-09-19 exact-head incident delta | Gap ID | 상태 | exact-head evidence | causal owner / next gate | diff --git a/requirements-strix-ci-hashes.txt b/requirements-strix-ci-hashes.txt index eb83beda17..969c12b602 100644 --- a/requirements-strix-ci-hashes.txt +++ b/requirements-strix-ci-hashes.txt @@ -144,6 +144,7 @@ anyio==4.14.2 \ --hash=sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494 \ --hash=sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f # via + # -r requirements-strix-ci.txt # google-genai # gql # httpx @@ -1811,10 +1812,12 @@ pygments==2.20.0 \ --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 # via rich -pyjwt==2.13.0 \ - --hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \ - --hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728 - # via mcp +pyjwt==2.14.0 \ + --hash=sha256:77283c83fb56ecf566a886c757a714bc83668e38156de2cce8263302f42e0b86 \ + --hash=sha256:ad0cef71c756a56e74863c2919cf0985f72decbcfcb550ee2f422e7c62b5eedc + # via + # -r requirements-strix-ci.txt + # mcp pyopenssl==26.4.0 \ --hash=sha256:28dfcce0162b9211413e26dfbfdf1d24317fbeba18fc93c12400a1856b2a0bc7 \ --hash=sha256:f0eb0cb2d581d3ad2b9c489468485e7f2ab6727d08401bcf9d824c3caddf3c1c diff --git a/requirements-strix-ci.txt b/requirements-strix-ci.txt index 50e8a05f9b..d4dd336d19 100644 --- a/requirements-strix-ci.txt +++ b/requirements-strix-ci.txt @@ -1,5 +1,6 @@ strix-agent==1.5.3 anyio==4.14.2 +pyjwt==2.14.0 openai[httpx2]==2.54.0 aiohttp==3.14.3 google-cloud-aiplatform==1.133.0 diff --git a/tests/fixtures/coverage-cargo/Cargo.lock b/tests/fixtures/coverage-cargo/Cargo.lock index 9a54521941..20666db700 100644 --- a/tests/fixtures/coverage-cargo/Cargo.lock +++ b/tests/fixtures/coverage-cargo/Cargo.lock @@ -2,18 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "cfg-if" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" - [[package]] name = "coverage-cargo-fixtures" version = "0.0.0" @@ -29,15 +17,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "itoa" version = "1.0.15" @@ -50,15 +29,6 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "once_cell" version = "1.21.4" @@ -82,37 +52,32 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.22.6" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" +checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b" dependencies = [ - "cfg-if", - "indoc", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.22.6" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" +checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.22.6" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" +checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327" dependencies = [ "libc", "pyo3-build-config", @@ -120,9 +85,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.6" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" +checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -132,13 +97,12 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.22.6" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" +checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952" dependencies = [ "heck", "proc-macro2", - "pyo3-build-config", "quote", "syn", ] @@ -152,12 +116,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - [[package]] name = "ryu" version = "1.0.20" @@ -177,18 +135,12 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "unicode-ident" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" - -[[package]] -name = "unindent" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" diff --git a/tests/fixtures/coverage-cargo/Cargo.toml b/tests/fixtures/coverage-cargo/Cargo.toml index dbe13ab8ab..67af5592ca 100644 --- a/tests/fixtures/coverage-cargo/Cargo.toml +++ b/tests/fixtures/coverage-cargo/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" [dependencies] itoa = "=1.0.15" ryu = "=1.0.20" -pyo3 = { version = "=0.22.6", features = ["extension-module", "abi3-py310"] } +pyo3 = { version = "=0.29.2", features = ["extension-module", "abi3-py310"] } From 02c70d519a8cf0ca36116c499b42987fbc1b9276 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 30 Sep 2026 13:29:56 +0900 Subject: [PATCH 6/7] test(security): reject duplicate vulnerable dependency locks --- ...d-security-baseline-pyjwt-pyo3-20260930.md | 10 +++++--- ...test_rust_coverage_fixture_dependencies.py | 25 ++++++++++++++++--- tests/test_strix_runtime_dependencies.py | 16 ++++++++++-- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/docs/doctoring/shared-security-baseline-pyjwt-pyo3-20260930.md b/docs/doctoring/shared-security-baseline-pyjwt-pyo3-20260930.md index 0b1eb5a725..c6c3ff67b6 100644 --- a/docs/doctoring/shared-security-baseline-pyjwt-pyo3-20260930.md +++ b/docs/doctoring/shared-security-baseline-pyjwt-pyo3-20260930.md @@ -18,7 +18,7 @@ at exact head `6f645a73502e159d5a229805afa34868ad9bb851` against protected job `109380628690`, reported PyO3 `0.22.6` in `tests/fixtures/coverage-cargo/Cargo.lock`: GHSA-36hh-v3qg-5jq4 (High, 8.0) and GHSA-chgr-c6px-7xpp (Medium, 5.5). Both advisories fix the defect in - PyO3 `0.29.0`; this repair selects the already reviewed `0.29.2` release. + PyO3 `0.29.0`; this repair selects and locally verifies `0.29.2`. - [Python Security run 36495499871](https://github.com/ContextualWisdomLab/.github/actions/runs/36495499871), job `109380725819`, reported PyJWT `2.13.0` in `requirements-strix-ci-hashes.txt` as affected by CVE-2026-102274. PyJWT @@ -72,8 +72,12 @@ cause is repaired here; no bypass or mutable source reference is permitted. 48 passed, 1 skipped. - Repository regression suite: 5,160 passed, 11 skipped, 40 subtests passed. - Rust `1.97.1` `cargo check --locked`: passed for the coverage fixture. -- Python lock regeneration from the existing reviewed lock: byte-identical; - hash-enforced installation loaded PyJWT `2.14.0`. +- Python lock regeneration with `uv 0.12.18`, seeded with the existing reviewed + output, was byte-identical. Input SHA-256 values were `c3812261…` for + `requirements-strix-ci.txt` and `3b745514…` for the override; the output was + `8f8318d4…`. A hash-enforced installation loaded PyJWT `2.14.0`. A fresh + unseeded solve is intentionally not claimed to be byte-identical because it + may select newer allowed transitive releases. - `pip-audit`: no known vulnerabilities in the Strix lock. OSV's direct `pyo3@0.29.2` query returned no vulnerability records. diff --git a/tests/test_rust_coverage_fixture_dependencies.py b/tests/test_rust_coverage_fixture_dependencies.py index 013bf77f33..9e6caa5d22 100644 --- a/tests/test_rust_coverage_fixture_dependencies.py +++ b/tests/test_rust_coverage_fixture_dependencies.py @@ -2,6 +2,11 @@ from pathlib import Path +try: + import tomllib +except ModuleNotFoundError: # pragma: no cover - Python 3.10 compatibility + import tomli as tomllib + REPOSITORY_ROOT = Path(__file__).resolve().parents[1] FIXTURE_ROOT = REPOSITORY_ROOT / "tests" / "fixtures" / "coverage-cargo" @@ -9,8 +14,20 @@ def test_coverage_fixture_uses_patched_pyo3_release() -> None: """Keep the fixture manifest and lock on the reviewed PyO3 0.29.2 release.""" - manifest = (FIXTURE_ROOT / "Cargo.toml").read_text(encoding="utf-8") - lock = (FIXTURE_ROOT / "Cargo.lock").read_text(encoding="utf-8") + manifest = tomllib.loads( + (FIXTURE_ROOT / "Cargo.toml").read_text(encoding="utf-8") + ) + cargo_lock = tomllib.loads( + (FIXTURE_ROOT / "Cargo.lock").read_text(encoding="utf-8") + ) + pyo3_packages = [ + package_entry + for package_entry in cargo_lock["package"] + if package_entry["name"] == "pyo3" + ] - assert 'pyo3 = { version = "=0.29.2"' in manifest - assert 'name = "pyo3"\nversion = "0.29.2"' in lock + assert manifest["dependencies"]["pyo3"]["version"] == "=0.29.2" + assert {package_entry["version"] for package_entry in pyo3_packages} == { + "0.29.2" + } + assert len(pyo3_packages) == 1 diff --git a/tests/test_strix_runtime_dependencies.py b/tests/test_strix_runtime_dependencies.py index 755ceab855..5784c48b44 100644 --- a/tests/test_strix_runtime_dependencies.py +++ b/tests/test_strix_runtime_dependencies.py @@ -3,6 +3,18 @@ REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +def _locked_requirement_versions(requirements_text: str, package_name: str) -> list[str]: + """Return every exact version row for one normalized package name.""" + package_versions = [] + for requirement_line in requirements_text.splitlines(): + requirement_name, separator, version_and_hash_marker = ( + requirement_line.strip().partition("==") + ) + if separator and requirement_name.casefold() == package_name.casefold(): + package_versions.append(version_and_hash_marker.split()[0]) + return package_versions + + def test_strix_installs_openai_httpx2_runtime() -> None: requirements = (REPOSITORY_ROOT / "requirements-strix-ci.txt").read_text( encoding="utf-8" @@ -38,5 +50,5 @@ def test_strix_pyjwt_security_pin_is_an_explicit_lock_input() -> None: REPOSITORY_ROOT / "requirements-strix-ci-hashes.txt" ).read_text(encoding="utf-8") - assert "pyjwt==2.14.0" in requirements.splitlines() - assert "pyjwt==2.14.0 \\" in requirements_lock.splitlines() + assert _locked_requirement_versions(requirements, "pyjwt") == ["2.14.0"] + assert _locked_requirement_versions(requirements_lock, "pyjwt") == ["2.14.0"] From d1aa3659fca527a6c7330151f3ab4df3d7578391 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 30 Sep 2026 14:02:20 +0900 Subject: [PATCH 7/7] test(security): normalize requirement extras --- tests/test_strix_runtime_dependencies.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_strix_runtime_dependencies.py b/tests/test_strix_runtime_dependencies.py index 5784c48b44..e7bc3462a5 100644 --- a/tests/test_strix_runtime_dependencies.py +++ b/tests/test_strix_runtime_dependencies.py @@ -10,11 +10,22 @@ def _locked_requirement_versions(requirements_text: str, package_name: str) -> l requirement_name, separator, version_and_hash_marker = ( requirement_line.strip().partition("==") ) + requirement_name = requirement_name.split("[", 1)[0].strip() if separator and requirement_name.casefold() == package_name.casefold(): package_versions.append(version_and_hash_marker.split()[0]) return package_versions +def test_locked_requirement_versions_normalizes_extras() -> None: + """Treat extras as the same distribution when detecting duplicate pins.""" + requirements = "pyjwt==2.14.0\npyjwt[crypto]==2.13.0\n" + + assert _locked_requirement_versions(requirements, "pyjwt") == [ + "2.14.0", + "2.13.0", + ] + + def test_strix_installs_openai_httpx2_runtime() -> None: requirements = (REPOSITORY_ROOT / "requirements-strix-ci.txt").read_text( encoding="utf-8"