diff --git a/.github/workflows/codeql-pr.yml b/.github/workflows/codeql-pr.yml index 2a170fa8a..cda5e7f62 100644 --- a/.github/workflows/codeql-pr.yml +++ b/.github/workflows/codeql-pr.yml @@ -90,13 +90,13 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: category: "/language:${{ matrix.language }}" upload: false @@ -197,13 +197,13 @@ jobs: ref: ${{ format('refs/pull/{0}/merge', github.event.pull_request.number) }} - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: category: "/language:${{ matrix.language }}-merge" upload: false diff --git a/.github/workflows/opencode-coverage-diagnostics-ci.yml b/.github/workflows/opencode-coverage-diagnostics-ci.yml new file mode 100644 index 000000000..56b714f7d --- /dev/null +++ b/.github/workflows/opencode-coverage-diagnostics-ci.yml @@ -0,0 +1,176 @@ +name: OpenCode Coverage Diagnostics CI + +on: + pull_request: + branches: [main] + paths: + - "scripts/ci/coverage_failure_summary.py" + - "scripts/ci/materialize_base_javascript_packages.py" + - "scripts/ci/materialize_base_python_requirements.py" + - "scripts/ci/sanitize_github_output_summary.py" + - "tests/test_materialize_base_javascript_packages.py" + - "tests/test_materialize_base_python_requirements.py" + - "tests/test_coverage_materializer_failure_diagnostics.py" + - "tests/test_coverage_native_fuzz_lock_boundary.py" + - "tests/test_sanitize_github_output_summary.py" + - "tests/test_strix_dependency_security_floor.py" + - "requirements-opencode-review-ci-hashes.txt" + - "requirements-strix-ci.txt" + - "requirements-strix-ci-hashes.txt" + - "pyproject.toml" + - ".github/workflows/opencode-coverage-diagnostics-ci.yml" + push: + branches: [main] + paths: + - "scripts/ci/coverage_failure_summary.py" + - "scripts/ci/materialize_base_javascript_packages.py" + - "scripts/ci/materialize_base_python_requirements.py" + - "scripts/ci/sanitize_github_output_summary.py" + - "tests/test_materialize_base_javascript_packages.py" + - "tests/test_materialize_base_python_requirements.py" + - "tests/test_coverage_materializer_failure_diagnostics.py" + - "tests/test_coverage_native_fuzz_lock_boundary.py" + - "tests/test_sanitize_github_output_summary.py" + - "tests/test_strix_dependency_security_floor.py" + - "requirements-opencode-review-ci-hashes.txt" + - "requirements-strix-ci.txt" + - "requirements-strix-ci-hashes.txt" + - "pyproject.toml" + - ".github/workflows/opencode-coverage-diagnostics-ci.yml" + +concurrency: + group: opencode-coverage-diagnostics-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + minimum-python-contract: + name: Python 3.10 runtime contract + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact revision + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Set up minimum supported Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.10" + + - name: Compile production modules on Python 3.10 + run: | + python -m compileall -q \ + scripts/ci/coverage_failure_summary.py \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py + + - name: Exercise exact failure evidence on Python 3.10 + run: | + python - <<'PY' + import os + import pathlib + import tempfile + + from scripts.ci import materialize_base_javascript_packages as javascript_materializer + from scripts.ci import materialize_base_python_requirements as python_materializer + + with tempfile.TemporaryDirectory() as directory: + output = pathlib.Path(directory) / "github-output" + os.environ["GITHUB_OUTPUT"] = str(output) + exact_reason = ( + "current-head npm lock package-lock.json package " + "apps/desktop/node_modules/@types/react-dom must pin a registry " + "tarball and SHA-512 integrity" + ) + javascript_materializer._publish_coverage_failure_summary( + "Base JavaScript package lock materialization", + ValueError(exact_reason), + "Repair the lock and rerun coverage-evidence.", + ) + python_materializer._publish_coverage_failure_summary( + "Base Python lock materialization", + OSError("fixture \nCWL_COVERAGE_SUMMARY_EOF"), + "Repair the trusted lock and rerun coverage-evidence.", + ) + published = output.read_text(encoding="utf-8") + assert f"ValueError: {exact_reason}" in published + assert "OSError: fixture <unsafe> CWL_COVERAGE_SUMMARY_END" in published + assert published.count("coverage_summary<- + python -m pip install --disable-pip-version-check --require-hashes + -r requirements-opencode-review-ci-hashes.txt + + - name: Run diagnostics and lock contracts with full branch coverage + run: | + python -m pytest \ + tests/test_materialize_base_javascript_packages.py \ + tests/test_materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_coverage_native_fuzz_lock_boundary.py \ + tests/test_sanitize_github_output_summary.py \ + tests/test_strix_dependency_security_floor.py \ + --cov=scripts.ci.coverage_failure_summary \ + --cov=scripts.ci.materialize_base_javascript_packages \ + --cov=scripts.ci.materialize_base_python_requirements \ + --cov=scripts.ci.sanitize_github_output_summary \ + --cov-branch \ + --cov-fail-under=100 \ + -q + + - name: Enforce complete production docstrings + run: | + python -m interrogate \ + --fail-under 100 \ + scripts/ci/coverage_failure_summary.py \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py + + - name: Compile changed Python surfaces + run: | + python -m compileall -q \ + scripts/ci/coverage_failure_summary.py \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_coverage_native_fuzz_lock_boundary.py \ + tests/test_sanitize_github_output_summary.py \ + tests/test_strix_dependency_security_floor.py diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index d826ce67a..41748bcec 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -652,11 +652,15 @@ jobs: r-base \ r-cran-covr \ r-cran-testthat \ + llvm-19 \ rustc \ util-linux \ vulkan-tools \ xz-utils \ && rm -rf /var/lib/apt/lists/* + ENV LLVM_COV=/usr/bin/llvm-cov-19 + ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19 + RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA" RUN curl --proto '=https' --tlsv1.2 -fsSLo /tmp/node-linux-x64.tar.xz \ https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-x64.tar.xz \ && echo '55aa7153f9d88f28d765fcdad5ae6945b5c0f98a36881703817e4c450fa76742 /tmp/node-linux-x64.tar.xz' | sha256sum -c - \ diff --git a/.github/workflows/scheduled-security-scan.yml b/.github/workflows/scheduled-security-scan.yml index 8ecb5185b..331de634f 100644 --- a/.github/workflows/scheduled-security-scan.yml +++ b/.github/workflows/scheduled-security-scan.yml @@ -90,13 +90,13 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis continue-on-error: true - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: category: "/language:${{ matrix.language }}-scheduled" @@ -131,7 +131,7 @@ jobs: - name: Upload Trivy SARIF to code scanning if: always() && hashFiles('trivy-results.sarif') != '' continue-on-error: true - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/upload-sarif@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: sarif_file: trivy-results.sarif category: trivy-fs-scheduled diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..5345e2ab5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog + +All notable changes to the ContextualWisdomLab central GitHub control plane are documented in this file. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versioned releases follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Fixed + +- Keep the native Atheris fuzz-engine lock in dedicated repository fuzz workflows instead of installing it in the generic OpenCode coverage image; immutable hash-pinned property and regression test locks remain eligible for central coverage materialization. + +### Documentation + +- Add an APA 7 doctoring record for the generic coverage/native fuzz-engine dependency boundary, exact-base trust model, verification fixture, limitations, and rollback requirements. diff --git a/docs/doctoring/coverage-failure-diagnostics.md b/docs/doctoring/coverage-failure-diagnostics.md new file mode 100644 index 000000000..0031ebe33 --- /dev/null +++ b/docs/doctoring/coverage-failure-diagnostics.md @@ -0,0 +1,27 @@ +# Credential-redacted coverage failure diagnostics + +## Decision + +Coverage setup failures are security-relevant review evidence, but exception text is untrusted and may contain registry URL userinfo, authorization headers, API tokens, database connection strings, passwords, or encryption keys. JavaScript and Python trusted-lock materializers therefore delegate multiline `GITHUB_OUTPUT` publication to one shared helper. The helper normalizes whitespace, applies the central credential sanitizer, bounds each field, HTML-escapes Markdown-embedded evidence, and replaces the fixed multiline delimiter before publication. + +The sanitizer applies URL-userinfo and authorization-header redaction before key-value truncation so mixed single-line failures cannot preserve an earlier credential. The final output retains the failure class, stage, bounded non-secret context, and remediation without exposing raw credentials. Local CLI status remains nonzero when publication is unavailable. + +## Verification contract + +The exact-head gate requires Python 3.10 compilation, Python 3.14 tests, 100% production statement and branch coverage, 100% production docstrings, and direct execution of the shared sanitizer CLI contract. Regression cases cover mixed URL, bearer, and token secrets; delimiter injection; oversized errors; missing `GITHUB_OUTPUT`; and both materializer call paths. Temporary write-capable repair workflows are removed from the final tree. + +## Standards and guidance + +GitHub environment files define delimiter-based multiline outputs and warn that a delimiter must not occur alone within arbitrary values. This implementation delimiter-proofs bounded fields before writing `GITHUB_OUTPUT`. OWASP logging guidance recommends removing, masking, sanitizing, hashing, or encrypting access tokens, passwords, database connection strings, encryption keys, session identifiers, and sensitive personal data rather than recording them directly. RFC 3986 deprecates secret passwords in URI userinfo because URIs are commonly displayed, stored, and logged. + +## Limitations + +Pattern-based redaction is a defense-in-depth boundary, not a general secret classifier. Callers must not intentionally place secrets in exception messages. GitHub log masking and least-privilege workflow permissions remain required. The diagnostic helper does not make untrusted test output safe for shell evaluation or workflow-command execution. + +## References + +Berners-Lee, T., Fielding, R., & Masinter, L. (2005). *Uniform resource identifier (URI): Generic syntax* (RFC 3986). Internet Engineering Task Force. https://doi.org/10.17487/RFC3986 + +GitHub. (2026). *Workflow commands for GitHub Actions*. GitHub Docs. Retrieved August 5, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands + +OWASP Foundation. (n.d.). *Logging cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 5, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html diff --git a/docs/doctoring/coverage-native-fuzz-lock-boundary.md b/docs/doctoring/coverage-native-fuzz-lock-boundary.md new file mode 100644 index 000000000..f1a1b6c2b --- /dev/null +++ b/docs/doctoring/coverage-native-fuzz-lock-boundary.md @@ -0,0 +1,56 @@ +# Generic coverage versus native fuzz-engine lock boundary + +## Decision + +The central OpenCode coverage image materializes immutable, hash-pinned dependencies needed to import selected production modules and run their ordinary tests. It does not install native coverage-guided fuzz engines that are executed only by dedicated repository fuzz workflows. + +`requirements-atheris.txt` is therefore classified as a native fuzz-engine lock and excluded from generic coverage materialization. The classification is exact-name based and path-independent. Hash-pinned property and regression locks such as `requirements-property.txt` and `requirements-fuzz-regression.txt` remain eligible. + +## Technical rationale + +Atheris is a coverage-guided native Python fuzzer built on libFuzzer. Its runtime role is to instrument and repeatedly execute fuzz targets, not to provide application imports required by an ordinary coverage.py test run. Installing an interpreter- and platform-specific native fuzz runtime in every generic coverage image adds an unrelated native artifact compatibility gate before application coverage begins. + +Coverage.py measures execution of Python programs and can report statement and branch coverage for the selected test process without Atheris. The central reviewer therefore preserves two independent verification layers: + +1. repository Fuzz workflows install and execute the native fuzz engine against real fuzz targets; +2. central OpenCode coverage evidence installs ordinary import/test dependencies and measures the selected production surface. + +Separating these layers avoids converting a native fuzz toolchain mismatch into a source-coverage review failure while retaining both gates. + +## Trust boundary + +The materializer still reads every candidate only from the exact validated pull-request base commit. Pull-request-mutated dependency files never enter the networked image-build stage. Every included lock must remain hash-pinned, and malformed Git metadata, unsafe paths, non-blob entries, unpinned requirements, and unsafe output destinations remain fail-closed. + +The exclusion reduces trusted inputs. It does not introduce an unhashed fallback, download a replacement package, or suppress an application/test import failure. Dedicated Fuzz required workflows continue to install `requirements-atheris.txt` directly. + +## Verification evidence + +A real temporary Git repository fixture contains: + +- `fuzz/requirements-atheris.txt`; +- `fuzz/requirements-property.txt`; +- `services/example_service/requirements-fuzz-regression.txt`. + +The test commits these files as the immutable base, materializes that exact revision, and proves that only the property and regression locks appear in the generated manifest. A second contract proves exact-name classification so a substring or directory name cannot broaden the exclusion. + +The changed helper and integration path are subject to the central 100% statement, branch, and docstring gates. + +## Operational limits + +The exact-name set initially contains only `requirements-atheris.txt`. Another native engine must not be added through a wildcard or informal comment. It requires separate artifact-role evidence, a regression fixture, review, and changelog entry. + +This boundary does not claim that Atheris is optional for fuzzing. It is optional only for the generic OpenCode import/coverage image. Repositories remain responsible for realistic dedicated fuzz execution and crash-regression evidence. + +## Rollback + +Rollback removes the exact-name classifier and its fixture. Before rollback, operators must confirm that every supported central coverage interpreter can install every repository's Atheris lock and that doing so provides coverage evidence not already supplied by the dedicated Fuzz workflow. Otherwise rollback recreates the false-negative review condition documented here. + +## APA 7 references + +Batchelder, N. (2026). *Coverage.py documentation*. https://coverage.readthedocs.io/ + +Google. (2026). *Atheris: A coverage-guided, native Python fuzzer* [Computer software]. GitHub. https://github.com/google/atheris + +Python Packaging Authority. (2026). *Dependency specifiers*. Python Packaging User Guide. https://packaging.python.org/en/latest/specifications/dependency-specifiers/ + +Semgrep, Inc. (2026). *Sample continuous integration configurations*. https://semgrep.dev/docs/semgrep-ci/sample-ci-configs diff --git a/docs/doctoring/opencode-llvm-coverage-toolchain.md b/docs/doctoring/opencode-llvm-coverage-toolchain.md new file mode 100644 index 000000000..64129cf71 --- /dev/null +++ b/docs/doctoring/opencode-llvm-coverage-toolchain.md @@ -0,0 +1,45 @@ +# OpenCode LLVM coverage toolchain decision + +## Decision + +The central OpenCode coverage image installs Debian Trixie's `llvm-19` package and explicitly exports: + +```text +LLVM_COV=/usr/bin/llvm-cov-19 +LLVM_PROFDATA=/usr/bin/llvm-profdata-19 +``` + +The image build fails unless both paths are executable. This is required because the image uses Debian-packaged `rustc` rather than a rustup-managed toolchain, so `llvm-tools-preview` is not an available installation path. + +## Evidence and compatibility boundary + +`cargo-llvm-cov` documents `LLVM_COV` and `LLVM_PROFDATA` as the overrides to use when a Rust toolchain is installed outside rustup. It also requires the selected tools to be compatible with the LLVM version used by `rustc`. Its published compatibility table maps Rust 1.82–1.95 to LLVM 19–22. The central image therefore selects LLVM 19 as the lowest compatible family for its supported Rust range and keeps the two binary paths explicit rather than relying on an unversioned system default. + +Debian Trixie publishes `llvm-19` from the `llvm-toolchain-19` source package. The package version currently documented for amd64 is 19.1.7-3+b1. The workflow installs the package from the pinned Debian image repositories and verifies the exact versioned executable paths during image construction. + +## Security and reproducibility contract + +- Pull-request content cannot select another LLVM package or executable path. +- The coverage image definition remains default-branch controlled and is built from immutable workflow source. +- `LLVM_COV` and `LLVM_PROFDATA` are set together; partial configuration is rejected. +- Missing executables fail the image build before any pull-request coverage measurement starts. +- The image digest, workflow commit SHA, pull-request head SHA, and coverage artifacts remain independently addressable evidence. +- CPU coverage is a correctness gate. GPU execution and parity tests remain separate domain-specific gates and are not represented by LLVM host coverage alone. + +This design does not claim formal compliance with a software supply-chain standard. It establishes a narrow, auditable compatibility boundary for deterministic Rust coverage execution. + +## Regression contract + +The central workflow contract test must continue to prove that: + +1. `llvm-19` is installed in the coverage image; +2. `LLVM_COV` names `/usr/bin/llvm-cov-19`; +3. `LLVM_PROFDATA` names `/usr/bin/llvm-profdata-19`; +4. the image build checks both paths before installing or invoking `cargo-llvm-cov`; and +5. the OpenCode approval path remains fail-closed when Rust coverage cannot run. + +## References + +Debian Project. (2026). *Details of package llvm-19 in trixie*. https://packages.debian.org/trixie/amd64/llvm-19 + +Taiki Endo. (2026). *cargo-llvm-cov: Cargo subcommand to easily use LLVM source-based code coverage* [Computer software]. GitHub. https://github.com/taiki-e/cargo-llvm-cov diff --git a/docs/doctoring/strix-source-directory-boundary.md b/docs/doctoring/strix-source-directory-boundary.md new file mode 100644 index 000000000..738f05a4e --- /dev/null +++ b/docs/doctoring/strix-source-directory-boundary.md @@ -0,0 +1,48 @@ +# Strix source-directory boundary + +## Decision + +`STRIX_SOURCE_DIRS` is a scanner input boundary, not an arbitrary filesystem path list. The central Strix gate now accepts only `.` or direct child directory names whose characters are drawn from a known-good Unicode-aware allowlist. The normalized value is deduplicated, bounded to 32 entries and 8,192 input bytes, and frozen as a read-only shell variable before any path join occurs. + +Nested paths are intentionally not accepted. The gate already resolves and validates `STRIX_TARGET_PATH`; callers that need a nested scan root must select that root through the target-path contract and use `STRIX_SOURCE_DIRS=.`. This keeps one canonical trust boundary instead of composing two independently mutable path fragments. + +## Threat model + +Before this change, each whitespace-delimited `STRIX_SOURCE_DIRS` token was appended to the canonical target root. A caller-controlled absolute path could discard the intended root, while `..` components or nested symlink chains could resolve outside it. The subsequent recursive search could then read unrelated runner files and allow their content to influence a published Strix report. + +The protected boundary rejects: + +- absolute paths; +- `/` and `\\` separators; +- parent traversal and nested path components; +- shell glob and metacharacter input; +- option-like names beginning with `-`; +- control characters, tabs, and line breaks; +- overlong components, overlong lists, and excessive entry counts. + +Safe direct names remain internationalized: Unicode letters, combining marks, and numbers are accepted. The final candidate must still be a real non-symlink directory under the already-canonical scan target before recursive search begins. + +## Verification + +`tests/test_strix_model_utils_source_dirs.py` provides executable regressions for: + +- deterministic deduplication and order preservation; +- Korean direct-directory names; +- read-only post-validation state; +- relative and absolute traversal; +- nested paths and both path separators; +- glob, punctuation, option-like, control-character, size, and cardinality limits. + +The test was first executed against the prior helper and failed for traversal, absolute, nested, glob, punctuation, and duplicate inputs. It passes after the source-boundary contract is installed. The helper is also parsed with `bash -n`, and the Python regression module is compiled before publication. + +## Security properties and limits + +The change follows an accept-known-good strategy instead of attempting to remove dangerous substrings. It also avoids returning the rejected value in error messages. This prevents the common failure mode where filtering one traversal representation leaves another representation or where diagnostics disclose useful filesystem details. + +This control does not make arbitrary scanner output trustworthy. Strix findings remain untrusted data, provider failures remain fail-closed, PR-head materialization remains bounded to validated Git objects, and privileged workflow publication continues to require exact-head checks and repository protection. + +## References + +MITRE. (2026, April 30). *CWE-22: Improper limitation of a pathname to a restricted directory ('path traversal')* (Version 4.20). Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/22.html + +OWASP Foundation. (n.d.). *Path traversal*. Retrieved August 5, 2026, from https://owasp.org/www-community/attacks/Path_Traversal diff --git a/docs/superpowers/plans/2026-08-05-coverage-native-fuzz-lock-boundary.md b/docs/superpowers/plans/2026-08-05-coverage-native-fuzz-lock-boundary.md new file mode 100644 index 000000000..7f083022f --- /dev/null +++ b/docs/superpowers/plans/2026-08-05-coverage-native-fuzz-lock-boundary.md @@ -0,0 +1,129 @@ +# Coverage Native-Fuzz Lock Boundary Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Prevent native fuzz-engine toolchain locks from entering generic OpenCode coverage images while retaining hash-pinned property and test dependencies. + +**Architecture:** Add one exact-name lock-role classifier to the trusted-base Python dependency materializer and evaluate it before blob selection. Protect the boundary with real temporary-Git fixtures, 100% production statement/branch coverage, docstrings, and source-backed doctoring. + +**Tech Stack:** Python 3.10+, `pathlib`, Git CLI read-only commands, pytest, pytest-cov. + +## Global Constraints + +- Continue reading dependency metadata only from the validated base commit. +- Do not change OpenCode, Noema, Strix, NVIDIA NIM, or reviewer credential names/scopes. +- Do not weaken `--require-hashes`, output bounds, symlink rejection, or malformed-tree failure. +- Every changed production helper must have a docstring and 100% statement/branch coverage. +- Document current authoritative sources in APA 7 format. +- Update `CHANGELOG.md`. + +--- + +### Task 1: Add failing real-repository coverage-role evidence + +**Files:** +- Create: `tests/test_coverage_native_fuzz_lock_boundary.py` + +**Interfaces:** +- Consumes: `materializer.materialize(repo, base_sha, output)` +- Produces: a fixture proving `requirements-atheris.txt` is excluded while property/test locks remain + +- [ ] **Step 1: Create a temporary Git base with three hash locks** + +Add: + +- `fuzz/requirements-atheris.txt`; +- `fuzz/requirements-property.txt`; +- `services/example/requirements-fuzz-regression.txt`. + +- [ ] **Step 2: Assert only the latter two enter the manifest** + +The exact Atheris name must be absent. The nonexact similarly named lock proves the classifier is not substring-based. + +- [ ] **Step 3: Run the focused test and verify RED** + +Run: `python -m pytest tests/test_coverage_native_fuzz_lock_boundary.py -q` + +Expected: FAIL because all three files are currently selected. + +- [ ] **Step 4: Commit the failing test** + +```bash +git add tests/test_coverage_native_fuzz_lock_boundary.py +git commit -m "test(coverage): exclude native fuzz engine locks" +``` + +### Task 2: Implement the exact-name lock-role boundary + +**Files:** +- Modify: `scripts/ci/materialize_base_python_requirements.py` + +**Interfaces:** +- Produces: `_is_native_fuzz_engine_lock_name(name: str) -> bool` +- Updates: `_is_candidate_lock_name(name: str) -> bool` + +- [ ] **Step 1: Add an immutable exact-name set** + +The initial set contains only `requirements-atheris.txt`. + +- [ ] **Step 2: Add the pure classifier with explanatory docstring** + +Return true only for exact members of the immutable set. + +- [ ] **Step 3: Exclude the native toolchain before ordinary candidate matching** + +Keep every existing candidate and content check unchanged for other files, including the shared bounded and credential-redacting failure-summary publisher. + +- [ ] **Step 4: Run focused tests and verify GREEN** + +Run: `python -m pytest tests/test_coverage_native_fuzz_lock_boundary.py -q` + +Expected: PASS. + +- [ ] **Step 5: Run coverage and docstring gates** + +Run the repository's full Python test, branch-coverage, compile, formatting, static-security, and interrogate commands. + +- [ ] **Step 6: Commit** + +```bash +git add scripts/ci/materialize_base_python_requirements.py tests/test_coverage_native_fuzz_lock_boundary.py .github/workflows/opencode-coverage-diagnostics-ci.yml +git commit -m "fix(coverage): skip native fuzz engine locks" +``` + +### Task 3: Record doctoring and release evidence + +**Files:** +- Create: `docs/doctoring/coverage-native-fuzz-lock-boundary.md` +- Create or modify: `CHANGELOG.md` + +**Interfaces:** +- Produces: operational rationale, standards traceability, and Unreleased evidence + +- [ ] **Step 1: Document the role boundary** + +Record why Atheris belongs to dedicated fuzz execution rather than generic import coverage, the immutable-base trust boundary, limitations, and rollback. + +- [ ] **Step 2: Add APA 7 references** + +Cite official Atheris, Python packaging, coverage.py, and Semgrep/GitHub Actions material relevant to the decision. + +- [ ] **Step 3: Update the changelog** + +Add the generic coverage materializer correction under `Unreleased / Fixed`. + +- [ ] **Step 4: Run full exact-slice verification and commit** + +```bash +git add docs/doctoring/coverage-native-fuzz-lock-boundary.md CHANGELOG.md +git commit -m "docs(coverage): record native fuzz lock boundary" +``` + +### Task 4: Validate, review, and integrate + +- [ ] **Step 1: Open a focused PR closing #762** +- [ ] **Step 2: Resolve every automated and human review finding** +- [ ] **Step 3: Re-run all exact-head central checks and independent review** +- [ ] **Step 4: Merge prerequisite #759 without administrative bypass** +- [ ] **Step 5: Retarget this PR to integrated `main`, rerun exact-head gates, and merge without bypass** +- [ ] **Step 6: Re-dispatch coverage review for contextual-orchestrator #96, merge it when green, then continue its dependency-ordered PR queue** diff --git a/docs/superpowers/specs/2026-08-05-coverage-native-fuzz-lock-boundary-design.md b/docs/superpowers/specs/2026-08-05-coverage-native-fuzz-lock-boundary-design.md new file mode 100644 index 000000000..80228153d --- /dev/null +++ b/docs/superpowers/specs/2026-08-05-coverage-native-fuzz-lock-boundary-design.md @@ -0,0 +1,47 @@ +# Coverage Native-Fuzz Lock Boundary Design + +## Status + +Approved for autonomous implementation under issue #762. + +## Problem + +The central trusted-base dependency materializer currently treats every hash-pinned `requirements*.txt` file as part of the generic offline coverage image. That is too broad: native coverage-guided fuzz engines are execution toolchains for dedicated fuzz jobs, not import dependencies for ordinary statement/branch coverage. Selecting `requirements-atheris.txt` can make a coverage review fail on an interpreter-specific native wheel before any selected application or test code runs. + +## Decision + +Classify exact native fuzz-engine lock names as coverage-incompatible. The first supported name is `requirements-atheris.txt`. + +The classification is path-independent so standalone repositories and nested MSA modules receive the same treatment. Hash-pinned property/unit-test locks such as `requirements-property.txt` remain eligible. Exact-base Git reads, immutable-source selection, hash requirements, bounded output, and deterministic ordering remain unchanged. + +## Components + +### Lock-role classifier + +A pure helper receives one file name and returns whether it represents a native fuzz runtime. The helper uses an immutable exact-name set rather than substring matching, which avoids excluding unrelated dependencies such as `requirements-fuzz-regression.txt`. + +### Materializer integration + +`base_hash_locks` checks the role before reading or exporting the blob. Excluded native fuzz locks never enter the Docker build context or trusted coverage image. Dedicated repository fuzz workflows continue installing those locks directly. + +### Verification + +A real temporary Git repository proves that: + +- `fuzz/requirements-atheris.txt` is excluded; +- `fuzz/requirements-property.txt` remains materialized; +- a similarly named non-exact lock remains eligible; +- existing exact-base, hash, symlink, malformed-tree, uv, and CLI contracts remain green; +- changed production helpers retain 100% statement/branch coverage and docstrings. + +## Security boundary + +The change reduces trusted coverage-image inputs; it does not accept PR-controlled dependency metadata or weaken `--require-hashes`. Exclusion is limited to a toolchain that the generic coverage job never executes. Adding another excluded name requires a separate reviewed change, evidence, and test. + +## Non-goals + +- changing the dedicated Fuzz required workflow; +- changing OpenCode/Noema/Strix models or credentials; +- changing NVIDIA NIM keys; +- suppressing coverage failures in selected application/test dependencies; +- interpreting arbitrary requirement contents or comments as trusted role metadata. diff --git a/requirements-strix-ci-hashes.txt b/requirements-strix-ci-hashes.txt index e2c8f00eb..c305e9c84 100644 --- a/requirements-strix-ci-hashes.txt +++ b/requirements-strix-ci-hashes.txt @@ -4,127 +4,128 @@ aiohappyeyeballs==2.7.1 \ --hash=sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d \ --hash=sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472 # via aiohttp -aiohttp==3.14.1 \ - --hash=sha256:03ab4530fdcb3a543a122ba4b65ac9919da9fe9f78a03d328a6e38ff962f7aa5 \ - --hash=sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983 \ - --hash=sha256:092e4ce3619a7c6dee52a6bdabda973d9b34b66781f840ce93c7e0cec30cf521 \ - --hash=sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340 \ - --hash=sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d \ - --hash=sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a \ - --hash=sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4 \ - --hash=sha256:1c1421eb01d4fd608d88cc8290211d177a58532b55ad94076fb349c5bf467f0a \ - --hash=sha256:1c1af67559445498b502030c35c59db59966f47041ca9de5b4e707f86bd10b5f \ - --hash=sha256:1d459b98a932296c6f0e94f87511a0b1b90a8a02c30a50e60a297619cd5a58ee \ - --hash=sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8 \ - --hash=sha256:23119f8fd4f5d16902ed459b63b100bcd269628075162bddac56cc7b5273b3fb \ - --hash=sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397 \ - --hash=sha256:24ba13339fed9251d9b1a1bec8c7ab84c0d1675d79d33501e11f94f8b9a84e05 \ - --hash=sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8 \ - --hash=sha256:269b76ac5394092b95bc4a098f4fc6c191c083c3bd12775d1e30e663132f6a09 \ - --hash=sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2 \ - --hash=sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba \ - --hash=sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf \ - --hash=sha256:2aa92c87868cd13674989f9ee83e5f9f7ea4237589b728048e1f0c8f6caa3271 \ - --hash=sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5 \ - --hash=sha256:2c840c90759922cb5e6dda94596e079a30fb5a5ba548e7e0dc00574703940847 \ - --hash=sha256:2f73e01dc37122325caf079982621262f96d74823c179038a82fddfc50359264 \ - --hash=sha256:2fbc3ed048b3475b9f0cbcb9978e9d2d3511acd91ead203af26ed9f0056004cf \ - --hash=sha256:2fe3607e71acc6ebb0ec8e492a247bf7a291226192dc0084236dfc12478916f6 \ - --hash=sha256:30099eda75a53c32efb0920e9c33c195314d2cc1c680fbfd30894932ac5f27df \ - --hash=sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035 \ - --hash=sha256:313701e488100074ce99850404ee36e741abf6330179fec908a1944ecf570126 \ - --hash=sha256:317acd9f8602858dc7d59679812c376c7f0b97bcbbf16e0d6237f54141d8a8a6 \ - --hash=sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35 \ - --hash=sha256:34b257ec41345c1e8f2df68fa908a7952f5de932723871eb633ecbbff396c9a4 \ - --hash=sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333 \ - --hash=sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203 \ - --hash=sha256:3e6fc1a85fa7194a1a7d19f44e8609180f4a8eb5fa4c7ed8b4355f080fad235c \ - --hash=sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1 \ - --hash=sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251 \ - --hash=sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365 \ - --hash=sha256:486f7d16ed54c39c2cbd7ca71fd8ba2b8bb7860df65bd7b6ed640bab96a38a8b \ - --hash=sha256:4cd96b5ba05d67ed0cf00b5b405c8cd99586d8e3481e8ee0a831057591af7621 \ - --hash=sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94 \ - --hash=sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da \ - --hash=sha256:4f7215cb3933784f79ed20e5f050e15984f390424339b22375d5a53c933a0491 \ - --hash=sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe \ - --hash=sha256:52cdac9432d8b4a719f35094a818d95adcae0f0b4fe9b9b921909e0c87de9e7d \ - --hash=sha256:5663ee9257cfa1add7253a7da3035a02f31b6600ec48261585e1800a81533080 \ - --hash=sha256:57fc6745a4b7d0f5a9eb4f40a69718be6c0bc1b8368cc9fe89e90118719f4f42 \ - --hash=sha256:5a837f49d901f9e368651b676912bff1104ed8c1a83b280bcd7b29adccef5c9c \ - --hash=sha256:5c0b3e614340c889d575451696374c9d17affd54cd607ca0babed8f8c37b9397 \ - --hash=sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9 \ - --hash=sha256:5f2504bc0322437c9a1ff6d3333ca56c7477b727c995f036b976ae17b98372c8 \ - --hash=sha256:603a2c834142172ffddc054067f5ec0ca65d57a0aa98a71bc81952573208e345 \ - --hash=sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3 \ - --hash=sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602 \ - --hash=sha256:64c567bf9eaf664280116a8688f63016e6b32db2505908e2bdaca1b6438142f2 \ - --hash=sha256:672ac254412a24d0d0cf00a9e6c238877e4be5e5fa2d188832c1244f45f31966 \ - --hash=sha256:672b9d65f42eb877f5c3f234a4547e4e1a226ca8c2eed879bb34670a0ce51192 \ - --hash=sha256:686b6c0d3911ec387b444ddf5dc62fb7f7c0a7d5186a7861626496a5ab4aff95 \ - --hash=sha256:6f71173be42d3241d428f760122febb748de0623f44308a6f120d0dd9ec572e3 \ - --hash=sha256:6fd35beba67c4183b09375c5fff9accb47524191a244a99f95fd4472f5402c2b \ - --hash=sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444 \ - --hash=sha256:73f05ea02013e02512c3bf42714f1208c57168c779cc6fe23516e4543089d0a6 \ - --hash=sha256:764457a7be60825fb770a644852ff717bcbb5042f189f2bd16df61a81b3f6573 \ - --hash=sha256:797457503c2d426bee06eef808d07b31ede30b65e054444e7de64cad0061b7af \ - --hash=sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15 \ - --hash=sha256:7fb4bdf95b0561a79f259f9d28fbc109728c5ee7f27aff6391f0ca703a329abe \ - --hash=sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2 \ - --hash=sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496 \ - --hash=sha256:86a6dab78b0e43e2897a3bbe15745aa60dc5423ca437b7b0b164c069bf91b876 \ - --hash=sha256:87a5eea1b2a5e21e1ebdbb33ad4165359189327e63fc4e4894693e7f821ac817 \ - --hash=sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448 \ - --hash=sha256:8f6bb621e5863cfe8fe5ff5468002d200ec31f30f1280b259dc505b02595099e \ - --hash=sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6 \ - --hash=sha256:915fbb7b41b115192259f8c9ae58f3ddc444d2b5579917270211858e606a4afd \ - --hash=sha256:93b032b5ec3255473c143627d21a69ac74ae12f7f33974cb587c564d11b1066f \ - --hash=sha256:94da27378da0610e341c4d30de29a191672683cc82b8f9556e8f7c7212a020fe \ - --hash=sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c \ - --hash=sha256:97e704dcd26271f5bda3fa07c3ce0fb76d6d3f8659f4baa1a24442cc9ba177ca \ - --hash=sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c \ - --hash=sha256:9af6779bfb46abf124068327abcdf9ce95c9ef8287a3e8da76ccf2d0f16c28fa \ - --hash=sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc \ - --hash=sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0 \ - --hash=sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0 \ - --hash=sha256:aa00140699487bd435fde4342d85c94cb256b7cd3a5b9c3396c67f19922afda2 \ - --hash=sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844 \ - --hash=sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719 \ - --hash=sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1 \ - --hash=sha256:b238af795833d5731d049d82bc84b768ae6f8f97f0495963b3ed9935c5901cc3 \ - --hash=sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178 \ - --hash=sha256:b6feea921016eb3d4e04d65fc4e9ca402d1a3801f562aef94989f54694917af3 \ - --hash=sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95 \ - --hash=sha256:b821a1f7dedf7e37450654e620038ac3b2e81e8fa6ea269337e97101978ec730 \ - --hash=sha256:bb2c0c80d431c0d03f2c7dbf125150fedd4f0de17366a7ca33f7ccb822391842 \ - --hash=sha256:bb33777ea21e8b7ecde0e6fc84f598be0a1192eab1a63bc746d75aa75d38e7bd \ - --hash=sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d \ - --hash=sha256:bd869c427324e5cb15195793de951295710db28be7d818247f3097b4ab5d4b96 \ - --hash=sha256:bedb0cd073cc2dc035e30aeb99444389d3cd2113afe4ef9fcd23d439f5bade85 \ - --hash=sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1 \ - --hash=sha256:c6fa4dc7ad6f8109c70bb1499e589f76b0b792baf39f9b017eb92c8a81d0a199 \ - --hash=sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a \ - --hash=sha256:cb21957bb8aca671c1765e32f58164cf0c50e6bf41c0bbbd16da20732ecaf588 \ - --hash=sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec \ - --hash=sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004 \ - --hash=sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480 \ - --hash=sha256:d3b1a184a9a8f548a6b73f1e26b96b052193e4b3175ed7342aaf1151a1f00a04 \ - --hash=sha256:d44ec478e713ee7f29b439f7eb8dc2b9d4079e11ae114d2c2ac3d5daf30516c8 \ - --hash=sha256:d9d4e294455b23a68c9b8f042d0e8e377a265bcb15332753695f6e5b6819e0ce \ - --hash=sha256:de538791a80e5d862addbc183f70f0158ac9b9bb872bb147f1fd2a683691e087 \ - --hash=sha256:e4e5e0ae56914ecdbf446493addefc0159053dd53962cef37d7839f37f73d505 \ - --hash=sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780 \ - --hash=sha256:ec8dc383ee57ea3e883477dcca3f11b65d58199f1080acaf4cd6ad9a99698be4 \ - --hash=sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d \ - --hash=sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca \ - --hash=sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665 \ - --hash=sha256:f234b4deb12f3ad59127e037bc57c40c21e45b45282df7d3a55a0f409f595296 \ - --hash=sha256:f380468b09d2a81633ee863b0ec5648d364bd17bb8ecfb8c2f387f7ac1faf42c \ - --hash=sha256:f5e6ff2bdbb8f4cd3fbe41f99e25bbcd58e3bf9f13d3dd31a11e7917251cc77a \ - --hash=sha256:f7a16ef45b081454ef844502d87a848876c490c4cb5c650c230f6ec79ed2c1e7 \ - --hash=sha256:faccab372e66bc76d5731525e7f1143c922271725b9d38c9f97edcc66266b451 \ - --hash=sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3 +aiohttp==3.14.3 \ + --hash=sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39 \ + --hash=sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043 \ + --hash=sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b \ + --hash=sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d \ + --hash=sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf \ + --hash=sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f \ + --hash=sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7 \ + --hash=sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc \ + --hash=sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559 \ + --hash=sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f \ + --hash=sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929 \ + --hash=sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147 \ + --hash=sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9 \ + --hash=sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8 \ + --hash=sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf \ + --hash=sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7 \ + --hash=sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8 \ + --hash=sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85 \ + --hash=sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30 \ + --hash=sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553 \ + --hash=sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7 \ + --hash=sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86 \ + --hash=sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e \ + --hash=sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a \ + --hash=sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c \ + --hash=sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da \ + --hash=sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5 \ + --hash=sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d \ + --hash=sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100 \ + --hash=sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71 \ + --hash=sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22 \ + --hash=sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1 \ + --hash=sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479 \ + --hash=sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb \ + --hash=sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062 \ + --hash=sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661 \ + --hash=sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427 \ + --hash=sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32 \ + --hash=sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a \ + --hash=sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db \ + --hash=sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42 \ + --hash=sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a \ + --hash=sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd \ + --hash=sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06 \ + --hash=sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8 \ + --hash=sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228 \ + --hash=sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0 \ + --hash=sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919 \ + --hash=sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee \ + --hash=sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2 \ + --hash=sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f \ + --hash=sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43 \ + --hash=sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098 \ + --hash=sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c \ + --hash=sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371 \ + --hash=sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b \ + --hash=sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0 \ + --hash=sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f \ + --hash=sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100 \ + --hash=sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529 \ + --hash=sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc \ + --hash=sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c \ + --hash=sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41 \ + --hash=sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716 \ + --hash=sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33 \ + --hash=sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f \ + --hash=sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e \ + --hash=sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa \ + --hash=sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b \ + --hash=sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80 \ + --hash=sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646 \ + --hash=sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e \ + --hash=sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b \ + --hash=sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c \ + --hash=sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963 \ + --hash=sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae \ + --hash=sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25 \ + --hash=sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c \ + --hash=sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f \ + --hash=sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807 \ + --hash=sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a \ + --hash=sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f \ + --hash=sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d \ + --hash=sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82 \ + --hash=sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15 \ + --hash=sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0 \ + --hash=sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d \ + --hash=sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9 \ + --hash=sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19 \ + --hash=sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239 \ + --hash=sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0 \ + --hash=sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c \ + --hash=sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5 \ + --hash=sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b \ + --hash=sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4 \ + --hash=sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2 \ + --hash=sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9 \ + --hash=sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0 \ + --hash=sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883 \ + --hash=sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d \ + --hash=sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d \ + --hash=sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6 \ + --hash=sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3 \ + --hash=sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924 \ + --hash=sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde \ + --hash=sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787 \ + --hash=sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb \ + --hash=sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b \ + --hash=sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0 \ + --hash=sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910 \ + --hash=sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9 \ + --hash=sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627 \ + --hash=sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48 \ + --hash=sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b \ + --hash=sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce \ + --hash=sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a \ + --hash=sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0 \ + --hash=sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24 \ + --hash=sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5 # via + # -r requirements-strix-ci.txt # gql # litellm aiosignal==1.4.0 \ @@ -401,53 +402,53 @@ click==8.4.1 \ # litellm # typer # uvicorn -cryptography==49.0.0 \ - --hash=sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001 \ - --hash=sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122 \ - --hash=sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6 \ - --hash=sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c \ - --hash=sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325 \ - --hash=sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69 \ - --hash=sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d \ - --hash=sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36 \ - --hash=sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc \ - --hash=sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6 \ - --hash=sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b \ - --hash=sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27 \ - --hash=sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61 \ - --hash=sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18 \ - --hash=sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db \ - --hash=sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b \ - --hash=sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb \ - --hash=sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2 \ - --hash=sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459 \ - --hash=sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e \ - --hash=sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21 \ - --hash=sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8 \ - --hash=sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7 \ - --hash=sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa \ - --hash=sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9 \ - --hash=sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db \ - --hash=sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64 \ - --hash=sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505 \ - --hash=sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5 \ - --hash=sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615 \ - --hash=sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f \ - --hash=sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866 \ - --hash=sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6 \ - --hash=sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561 \ - --hash=sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838 \ - --hash=sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9 \ - --hash=sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7 \ - --hash=sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68 \ - --hash=sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8 \ - --hash=sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3 \ - --hash=sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e \ - --hash=sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a \ - --hash=sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d \ - --hash=sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4 \ - --hash=sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493 \ - --hash=sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b +cryptography==50.0.0 \ + --hash=sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03 \ + --hash=sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7 \ + --hash=sha256:07479a1cb08219ab719147e742e76090c9c773321959bb94946fffdd397a6437 \ + --hash=sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987 \ + --hash=sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025 \ + --hash=sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037 \ + --hash=sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269 \ + --hash=sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105 \ + --hash=sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc \ + --hash=sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95 \ + --hash=sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b \ + --hash=sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47 \ + --hash=sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c \ + --hash=sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41 \ + --hash=sha256:5e34edd123674534acd70147f0ca331eaa2c74e6325fb2028c886aa26ba0b68c \ + --hash=sha256:62598a8a57f815db4c6259a4e97d857dab56697e7de8e8ab02352ab74da1995d \ + --hash=sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7 \ + --hash=sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c \ + --hash=sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708 \ + --hash=sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef \ + --hash=sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f \ + --hash=sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f \ + --hash=sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a \ + --hash=sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f \ + --hash=sha256:8eb5e1172eb569ea8a872796576e6a67c276351728b6455d5beb01242b027c6a \ + --hash=sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a \ + --hash=sha256:910d11e1a385c654bf738bf3e6b8e6ed5de0f5610fcae2be9e5b398d8081d20e \ + --hash=sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3 \ + --hash=sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d \ + --hash=sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3 \ + --hash=sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f \ + --hash=sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae \ + --hash=sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30 \ + --hash=sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9 \ + --hash=sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9 \ + --hash=sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07 \ + --hash=sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba \ + --hash=sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3 \ + --hash=sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f \ + --hash=sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533 \ + --hash=sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5 \ + --hash=sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11 \ + --hash=sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9 \ + --hash=sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f \ + --hash=sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169 \ + --hash=sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645 # via # -r requirements-strix-ci.txt # google-auth @@ -1680,9 +1681,9 @@ pyjwt==2.13.0 \ --hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \ --hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728 # via mcp -pyopenssl==26.3.0 \ - --hash=sha256:46367f8f66b92271e6d218da9c87607e1ef5a0bc5c8dea5bb3db82f395c385a3 \ - --hash=sha256:589de7fae1c9ea670d18422ed00fc04da787bbde8e1454aea872aa57b49ad341 +pyopenssl==26.4.0 \ + --hash=sha256:28dfcce0162b9211413e26dfbfdf1d24317fbeba18fc93c12400a1856b2a0bc7 \ + --hash=sha256:f0eb0cb2d581d3ad2b9c489468485e7f2ab6727d08401bcf9d824c3caddf3c1c # via google-auth python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ diff --git a/requirements-strix-ci.txt b/requirements-strix-ci.txt index e32bd39a9..98e5c33e2 100644 --- a/requirements-strix-ci.txt +++ b/requirements-strix-ci.txt @@ -1,6 +1,7 @@ strix-agent==1.0.4 +aiohttp==3.14.3 google-cloud-aiplatform==1.133.0 protobuf<7.0.0 -cryptography==49.0.0 +cryptography==50.0.0 python-multipart==0.0.32 pyasn1==0.6.4 diff --git a/scripts/ci/coverage_failure_summary.py b/scripts/ci/coverage_failure_summary.py new file mode 100644 index 000000000..5bbf45835 --- /dev/null +++ b/scripts/ci/coverage_failure_summary.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"""Publish bounded, credential-redacted coverage setup failure evidence.""" + +from __future__ import annotations + +import html +import os +from pathlib import Path + +from scripts.ci.sanitize_github_output_summary import sanitize_text + +_COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" + + +def _safe_field(value: str, maximum_length: int) -> str: + """Normalize, redact, bound, escape, and delimiter-proof one output field.""" + + normalized = " ".join(value.split()) + redacted = sanitize_text(normalized)[:maximum_length] + escaped = html.escape(redacted, quote=True) + return escaped.replace( + _COVERAGE_DELIMITER, + "CWL_COVERAGE_SUMMARY_END", + ) + + +def publish_coverage_failure_summary( + stage: str, + error: BaseException, + remediation: str, +) -> None: + """Append one safe exact-stage failure envelope to ``GITHUB_OUTPUT``.""" + + github_output = os.environ.get("GITHUB_OUTPUT") + if not github_output: + return + + safe_stage = _safe_field(stage, 256) + safe_reason = _safe_field( + f"{error.__class__.__name__}: {error}", + 4096, + ) + safe_remediation = _safe_field(remediation, 1024) + summary = ( + "## Coverage Decision\n" + "- Result: FAIL\n" + f"- Failed stage: {safe_stage}\n" + "- Exact failure:\n" + f"
{safe_reason}
\n" + f"- Next action: {safe_remediation}\n" + ) + with Path(github_output).open("a", encoding="utf-8") as output: + output.write( + f"coverage_summary<<{_COVERAGE_DELIMITER}\n" + f"{summary}{_COVERAGE_DELIMITER}\n" + ) diff --git a/scripts/ci/materialize_base_javascript_packages.py b/scripts/ci/materialize_base_javascript_packages.py index 407c17aa1..611db20e8 100644 --- a/scripts/ci/materialize_base_javascript_packages.py +++ b/scripts/ci/materialize_base_javascript_packages.py @@ -20,6 +20,11 @@ from typing import Any +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2])) +from scripts.ci.coverage_failure_summary import ( + publish_coverage_failure_summary as _publish_coverage_failure_summary, +) + SHA_RE = re.compile(r"^[0-9a-fA-F]{40}$") PNPM_SPEC_RE = re.compile(r"^pnpm@[0-9]+\.[0-9]+\.[0-9]+(?:[+-][A-Za-z0-9._+-]+)?$") PNPM_BASE_INPUT_NAMES = ("package.json", "pnpm-workspace.yaml", ".pnpmfile.cjs") @@ -445,6 +450,13 @@ def main(argv: list[str] | None = None) -> int: f"::error::Could not materialize base JavaScript package locks: {exc}", file=sys.stderr, ) + _publish_coverage_failure_summary( + "Base JavaScript package lock materialization", + exc, + "Repair or regenerate the reported lock entry so every non-link " + "package selected for the networked cache is registry- and " + "SHA-512-bounded, then rerun the current-head coverage-evidence job.", + ) return 1 if manifest: diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py old mode 100644 new mode 100755 index 8158372df..10926d64a --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -14,15 +14,29 @@ import tempfile +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2])) +from scripts.ci.coverage_failure_summary import ( + publish_coverage_failure_summary as _publish_coverage_failure_summary, +) + SHA_RE = re.compile(r"^[0-9a-fA-F]{40}$") UV_EXPORT_TIMEOUT_SECONDS = 120 +NATIVE_FUZZ_ENGINE_LOCK_NAMES = frozenset({"requirements-atheris.txt"}) + + +def _is_native_fuzz_engine_lock_name(name: str) -> bool: + """Return whether a lock installs a native engine used only by fuzz jobs.""" + return name in NATIVE_FUZZ_ENGINE_LOCK_NAMES def _is_candidate_lock_name(name: str) -> bool: - """Return whether a file name is a possible pip requirements lock.""" - return name == "requirements.lock" or ( - fnmatch.fnmatch(name, "requirements*.txt") - and not fnmatch.fnmatch(name, "requirements-*-ci-hashes.txt") + """Return whether a file name is a possible coverage dependency lock.""" + return not _is_native_fuzz_engine_lock_name(name) and ( + name == "requirements.lock" + or ( + fnmatch.fnmatch(name, "requirements*.txt") + and not fnmatch.fnmatch(name, "requirements-*-ci-hashes.txt") + ) ) @@ -241,6 +255,12 @@ def main(argv: list[str] | None = None) -> int: print( f"::error::Could not materialize base Python locks: {exc}", file=sys.stderr ) + _publish_coverage_failure_summary( + "Base Python lock materialization", + exc, + "Repair the reported trusted lock or Git metadata boundary, then " + "rerun the current-head coverage-evidence job.", + ) return 1 if manifest: diff --git a/scripts/ci/sanitize_github_output_summary.py b/scripts/ci/sanitize_github_output_summary.py index 1a7036f75..5cf762a72 100644 --- a/scripts/ci/sanitize_github_output_summary.py +++ b/scripts/ci/sanitize_github_output_summary.py @@ -15,18 +15,19 @@ r"API[_-]?KEY|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|ENCRYPTION[_-]?KEY" r")[A-Z0-9_.-]*\b)(?P\s*[:=]\s*)" ) -URL_CREDENTIAL_RE = re.compile(r"(?i)\b([a-z][a-z0-9+.-]*://)([^/\s:@]+):([^@\s/]+)@") +URL_CREDENTIAL_RE = re.compile(r"(?i)\b([a-z][a-z0-9+.-]*://)([^/\s@]+)@") AUTH_HEADER_RE = re.compile(r"(?i)\b(Authorization\s*[:=]\s*)(Bearer|Basic)\s+[^\s,;]+") def sanitize_line(line: str) -> str: """Redact one log line while preserving the key and evidence context.""" - match = SECRET_KEY_RE.search(line) + sanitized = URL_CREDENTIAL_RE.sub(r"\1@", line) + sanitized = AUTH_HEADER_RE.sub(r"\1\2 ", sanitized) + match = SECRET_KEY_RE.search(sanitized) if match: - return f"{line[: match.end()]}" - line = URL_CREDENTIAL_RE.sub(r"\1@", line) - return AUTH_HEADER_RE.sub(r"\1\2 ", line) + return f"{sanitized[: match.end()]}" + return sanitized def sanitize_text(text: str) -> str: @@ -52,5 +53,10 @@ def main() -> int: return 0 -if __name__ == "__main__": - raise SystemExit(main()) +def _entrypoint(module_name: str) -> None: + """Run the file-oriented CLI only when executed as a script.""" + if module_name == "__main__": + raise SystemExit(main()) + + +_entrypoint(__name__) diff --git a/scripts/ci/strix_model_utils.sh b/scripts/ci/strix_model_utils.sh index 9f20eae67..80541c7c2 100755 --- a/scripts/ci/strix_model_utils.sh +++ b/scripts/ci/strix_model_utils.sh @@ -12,6 +12,85 @@ trim_whitespace() { printf '%s\n' "$value" } +sanitize_strix_source_dirs() { + local raw_source_dirs + raw_source_dirs="$(trim_whitespace "${1-}")" + if [ -z "$raw_source_dirs" ]; then + echo "ERROR: STRIX_SOURCE_DIRS must contain at least one safe direct directory name." >&2 + return 2 + fi + + python3 -I -S - "$raw_source_dirs" <<'PY' +from __future__ import annotations + +import sys +import unicodedata + +raw_source_dirs = sys.argv[1] +if len(raw_source_dirs.encode("utf-8")) > 8192 or any( + character in "\x00\r\n\t" for character in raw_source_dirs +): + print( + "ERROR: STRIX_SOURCE_DIRS must be a bounded space-separated directory list.", + file=sys.stderr, + ) + raise SystemExit(2) +entries = raw_source_dirs.split(" ") +entries = [entry for entry in entries if entry] +if not entries or len(entries) > 32: + print( + "ERROR: STRIX_SOURCE_DIRS must contain between 1 and 32 safe direct directory names.", + file=sys.stderr, + ) + raise SystemExit(2) + +allowed_ascii = frozenset("_.@+[]-") +normalized: list[str] = [] +seen: set[str] = set() +for entry in entries: + if entry == ".": + pass + elif ( + entry == ".." + or len(entry.encode("utf-8")) > 255 + or entry.startswith("-") + or "/" in entry + or "\\" in entry + or not all( + (character.isascii() and (character.isalnum() or character in allowed_ascii)) + or ( + not character.isascii() + and unicodedata.category(character)[0] in {"L", "M", "N"} + ) + for character in entry + ) + ): + print( + "ERROR: STRIX_SOURCE_DIRS accepts only '.' or safe direct directory names.", + file=sys.stderr, + ) + raise SystemExit(2) + if entry not in seen: + seen.add(entry) + normalized.append(entry) + +print(" ".join(normalized)) +PY +} + +# STRIX_SOURCE_DIRS is later split by the gate before joining each token to the +# already-canonical scan root. Freeze a lexical direct-child allowlist at source +# time so absolute paths, parent traversal, nested symlink chains, shell glob expansion, +# and option-like path ambiguity can never reach that join. +STRIX_SOURCE_DIRS_SANITIZED="$( + sanitize_strix_source_dirs "${STRIX_SOURCE_DIRS-.}" +)" || { + status=$? + return "$status" 2>/dev/null || exit "$status" +} +readonly STRIX_SOURCE_DIRS="$STRIX_SOURCE_DIRS_SANITIZED" +unset STRIX_SOURCE_DIRS_SANITIZED + sanitize_provider_name() { local provider provider="$(trim_whitespace "${1-}")" diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 3b001a921..814568bfd 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -609,7 +609,10 @@ copy_pr_head_blob_to_file() { is_supported_source_file() { case "$1" in - *.java | *.kt | *.kts | *.groovy | *.scala | *.py | *.js | *.jsx | *.ts | *.tsx | *.vue | *.yaml | *.yml | *.sh | *.sql | *.xml | *.json | *.html | *.css | *.md) + # Rust is an application security boundary for Tauri and native services. Keep changed Rust + # sources in the same PR-head scope as frontend IPC wrappers so findings are not inferred from + # an incomplete client-only view. + *.java | *.kt | *.kts | *.groovy | *.scala | *.rs | *.py | *.js | *.jsx | *.ts | *.tsx | *.vue | *.yaml | *.yml | *.sh | *.sql | *.xml | *.json | *.html | *.css | *.md) return 0 ;; Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile) @@ -623,7 +626,7 @@ is_supported_source_file() { is_dependency_manifest_path() { case "$1" in - pom.xml | */pom.xml | package.json | */package.json | package-lock.json | */package-lock.json | pnpm-lock.yaml | */pnpm-lock.yaml | yarn.lock | */yarn.lock | pyproject.toml | */pyproject.toml | requirements.txt | */requirements.txt | requirements-*.txt | */requirements-*.txt | uv.lock | */uv.lock) + pom.xml | */pom.xml | package.json | */package.json | package-lock.json | */package-lock.json | pnpm-lock.yaml | */pnpm-lock.yaml | yarn.lock | */yarn.lock | pyproject.toml | */pyproject.toml | requirements.txt | */requirements.txt | requirements-*.txt | */requirements-*.txt | uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock) return 0 ;; *) @@ -1185,6 +1188,13 @@ pull_request_scope_context_files() { for changed_file in "$@"; do normalized_changed_file="$(normalize_changed_file_path "$changed_file")" || return 2 case "$normalized_changed_file" in + # Standalone support tools and their tests are not application runtime + # surfaces. Injecting the backend router/service inventory for these files + # creates an incomplete synthetic application and can turn valid imports in + # the real PR-head tree into false missing-module findings. + backend/scripts/* | backend/tests/*) + : + ;; backend/*) if [[ "$normalized_changed_file" =~ ^backend/.+\.py$ ]]; then needs_backend_python=1 diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index b4d585b9e..50a09bf73 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -165,6 +165,7 @@ assert_strix_pr_scope_includes_deployment_context() { assert_file_contains "$GATE_SCRIPT" ".github/workflows/* | Dockerfile | Dockerfile.* | frontend/Dockerfile | frontend/next.config.ts | docker-compose*.yml | render.yaml" "strix gate recognizes deployment and CI files" assert_file_contains "$GATE_SCRIPT" "Dockerfile.test" "strix gate includes test-image Dockerfiles with workflow scan context" assert_file_contains "$GATE_SCRIPT" "Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile" "strix gate treats deployment files as source files" + assert_file_contains "$GATE_SCRIPT" "Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock" "strix gate includes Rust crate dependency and feature context" assert_file_contains "$GATE_SCRIPT" "backend/scripts/docker_entrypoint.sh" "strix gate includes the combined Docker image entrypoint with deployment context" assert_file_contains "$GATE_SCRIPT" "backend/api/auth.py" "strix gate includes backend auth context for deployment scans" assert_file_contains "$GATE_SCRIPT" "frontend/package-lock.json" "strix gate includes frontend dependency lock context" @@ -192,7 +193,7 @@ assert_strix_workflow_pr_trigger_hardened() { assert_equals "1" "$status_token_count" "strix workflow defines GITHUB_STATUS_TOKEN once so GitHub can parse repository_dispatch" assert_file_not_contains "$workflow_file" "github.event.pull_request.number == 240" "strix workflow must not hard-code repository-specific PR bypasses" assert_file_contains "$workflow_file" "models: read" "strix workflow grants only the GitHub Models read permission needed for Strix" - assert_file_contains "$workflow_file" "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6" "strix workflow pins actions/setup-python" + assert_file_contains "$workflow_file" "actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0" "strix workflow pins actions/setup-python" assert_file_contains "$workflow_file" 'python-version: "3.13"' "strix workflow runs Python steps on Python 3.13" assert_file_contains "$workflow_file" "Resolve trusted Strix source ref" "strix workflow resolves the central trusted Strix source ref" assert_file_contains "$workflow_file" "toJSON(job)" "strix workflow derives the trusted source from the job workflow context" @@ -6243,6 +6244,32 @@ run_filtered_gate_case_if_requested() { "Materialized PR-head changed-file scope" \ "repository_dispatch" ;; + pull-request-target-rust-file-uses-head-blob) + run_pull_request_target_head_scope_case \ + "pull-request-target-rust-file-uses-head-blob" \ + "src-tauri/src/commands.rs" \ + "const BASE_RUST_CONTEXT: &str = \"must not be scanned\";" \ + "const HEAD_RUST_CONTEXT: &str = \"must be scanned\";" \ + "0" \ + "0" \ + "__PR_SCOPE__" \ + "0" \ + "Materialized PR-head changed-file scope" + ;; + pull-request-target-backend-script-omits-app-context) + run_pull_request_target_head_scope_case \ + "pull-request-target-backend-script-omits-app-context" \ + "backend/scripts/disksage_copy_readiness_handoff.py" \ + "BASE_SUPPORT_CODE_SHOULD_NOT_BE_SCANNED" \ + "HEAD_SUPPORT_CODE_SHOULD_BE_SCANNED" \ + "0" \ + "0" \ + "__PR_SCOPE__" \ + "0" \ + "Materialized PR-head changed-file scope" \ + "pull_request_target" \ + "backend/api/webdav.py" + ;; *) record_failure "unknown STRIX_TEST_CASE_FILTER '${STRIX_TEST_CASE_FILTER:-}'" ;; @@ -6267,6 +6294,7 @@ run_pull_request_target_head_scope_case() { local expected_full_head_scope="${8-$disable_pr_scoping}" local expected_scope_message="${9-}" local github_event_name="${10-pull_request_target}" + local unexpected_scope_file="${11-}" local tmp_dir tmp_dir="$(mktemp -d)" @@ -6335,6 +6363,10 @@ else exit 68 fi fi +if [ -n "${FAKE_STRIX_UNEXPECTED_SCOPE_FILE:-}" ] && [ -e "$target_path/$FAKE_STRIX_UNEXPECTED_SCOPE_FILE" ]; then + echo "Error: unrelated application context leaked into bounded support-code scope ($target_path/$FAKE_STRIX_UNEXPECTED_SCOPE_FILE)" >&2 + exit 69 +fi echo "scan ok with PR head content" EOF chmod +x "$fake_strix" @@ -6349,6 +6381,10 @@ EOF echo 'seed' >README.md mkdir -p docs printf '%s\n' 'BASE_FULL_SCOPE_CONTEXT_SHOULD_NOT_BE_SCANNED' >docs/full-scope-context.md + if [ -n "$unexpected_scope_file" ]; then + mkdir -p "$(dirname -- "$unexpected_scope_file")" + printf '%s\n' 'UNRELATED_APPLICATION_CONTEXT_SHOULD_NOT_BE_SCANNED' >"$unexpected_scope_file" + fi if [ "$base_content" != "__ABSENT__" ]; then mkdir -p "$(dirname -- "$changed_file")" printf '%s\n' "$base_content" >"$changed_file" @@ -6396,6 +6432,7 @@ EOF FAKE_STRIX_EXPECTED_UNCHANGED_FILE="docs/full-scope-context.md" \ FAKE_STRIX_EXPECTED_UNCHANGED_CONTENT="HEAD_FULL_SCOPE_CONTEXT_SHOULD_BE_SCANNED" \ FAKE_STRIX_EXPECT_FULL_HEAD_SCOPE="$expected_full_head_scope" \ + FAKE_STRIX_UNEXPECTED_SCOPE_FILE="$unexpected_scope_file" \ STRIX_DISABLE_PR_SCOPING="$disable_pr_scoping" \ STRIX_LLM_FILE="$strix_llm_file" \ LLM_API_KEY_FILE="$llm_api_key_file" \ @@ -8928,6 +8965,19 @@ run_pull_request_target_head_scope_case \ "0" \ "__PR_SCOPE__" +run_pull_request_target_head_scope_case \ + "pull-request-target-backend-script-omits-app-context" \ + "backend/scripts/disksage_copy_readiness_handoff.py" \ + "BASE_SUPPORT_CODE_SHOULD_NOT_BE_SCANNED" \ + "HEAD_SUPPORT_CODE_SHOULD_BE_SCANNED" \ + "0" \ + "0" \ + "__PR_SCOPE__" \ + "0" \ + "Materialized PR-head changed-file scope" \ + "pull_request_target" \ + "backend/api/webdav.py" + run_pull_request_target_head_scope_case \ "repository-dispatch-pr-scope-uses-head-blob" \ "backend/db/models.py" \ @@ -8946,6 +8996,17 @@ run_pull_request_target_head_scope_case \ "__ABSENT__" \ "HEAD_ONLY_NEW_FILE_SHOULD_BE_SCANNED" +run_pull_request_target_head_scope_case \ + "pull-request-target-rust-file-uses-head-blob" \ + "src-tauri/src/commands.rs" \ + "const BASE_RUST_CONTEXT: &str = \"must not be scanned\";" \ + "const HEAD_RUST_CONTEXT: &str = \"must be scanned\";" \ + "0" \ + "0" \ + "__PR_SCOPE__" \ + "0" \ + "Materialized PR-head changed-file scope" + run_pull_request_target_head_scope_case \ "pull-request-target-source-file-with-space-uses-head-blob" \ "src/unsafe name.py" \ diff --git a/tests/test_coverage_materializer_failure_diagnostics.py b/tests/test_coverage_materializer_failure_diagnostics.py new file mode 100644 index 000000000..722e58728 --- /dev/null +++ b/tests/test_coverage_materializer_failure_diagnostics.py @@ -0,0 +1,251 @@ +from __future__ import annotations + +import json +from pathlib import Path +from types import ModuleType +from typing import Callable + +import pytest + +from scripts.ci import materialize_base_javascript_packages as javascript_materializer +from scripts.ci import materialize_base_python_requirements as python_materializer + + +def _failing_materializer(error: BaseException) -> Callable[..., None]: + """Return a materializer stub that raises the supplied failure.""" + + def fail(*_args: object, **_kwargs: object) -> None: + raise error + + return fail + + +def _run_main(module: ModuleType, tmp_path: Path) -> int: + """Invoke one materializer CLI with a valid-shaped isolated argument set.""" + return module.main( + [ + "--repo-root", + str(tmp_path), + "--base-sha", + "a" * 40, + "--output-dir", + str(tmp_path / "output"), + ] + ) + + +def test_javascript_failure_publishes_exact_coverage_reason( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """The deterministic review receives the exact early npm-lock failure.""" + output_file = tmp_path / "github-output" + exact_reason = ( + "current-head npm lock package-lock.json package " + "apps/desktop/node_modules/@types/react-dom must pin a registry " + "tarball and SHA-512 integrity" + ) + monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) + monkeypatch.setattr( + javascript_materializer, + "materialize", + _failing_materializer(ValueError(exact_reason)), + ) + + assert _run_main(javascript_materializer, tmp_path) == 1 + + published = output_file.read_text(encoding="utf-8") + assert "coverage_summary< None: + """Early Python-lock failures remain concrete without output-file injection.""" + output_file = tmp_path / "github-output" + monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) + monkeypatch.setattr( + python_materializer, + "materialize", + _failing_materializer( + OSError( + "fixture \nCWL_COVERAGE_SUMMARY_EOF " + ("x" * 5000) + ) + ), + ) + + assert _run_main(python_materializer, tmp_path) == 1 + + published = output_file.read_text(encoding="utf-8") + assert "- Failed stage: Base Python lock materialization" in published + assert "OSError: fixture <unsafe> CWL_COVERAGE_SUMMARY_END" in published + assert "" not in published + assert published.count("CWL_COVERAGE_SUMMARY_EOF\n") == 2 + assert len(published) < 5000 + + +@pytest.mark.parametrize( + "module", + [javascript_materializer, python_materializer], + ids=["javascript", "python"], +) +def test_materializer_failure_summary_redacts_mixed_credentials( + module: ModuleType, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Every materializer removes mixed credentials before GitHub publication.""" + output_file = tmp_path / "github-output" + secret_values = ("url-secret", "bearer-secret", "token-secret") + reason = ( + "failure https://alice:url-secret@example.invalid/a.tgz " + "Authorization: Bearer bearer-secret TOKEN=token-secret trailing context" + ) + monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) + monkeypatch.setattr( + module, + "materialize", + _failing_materializer(RuntimeError(reason)), + ) + + assert _run_main(module, tmp_path) == 1 + + published = output_file.read_text(encoding="utf-8") + assert "https://<redacted>@example.invalid/a.tgz" in published + assert "Authorization: Bearer <redacted>" in published + assert "TOKEN=<redacted>" in published + for secret_value in secret_values: + assert secret_value not in published + + +@pytest.mark.parametrize( + "module", + [javascript_materializer, python_materializer], + ids=["javascript", "python"], +) +def test_failure_diagnostics_are_optional_outside_github_actions( + module: ModuleType, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Local CLI failures keep their status when no Actions output file exists.""" + monkeypatch.delenv("GITHUB_OUTPUT", raising=False) + monkeypatch.setattr( + module, + "materialize", + _failing_materializer(RuntimeError("local fixture failure")), + ) + + assert _run_main(module, tmp_path) == 1 + assert not (tmp_path / "github-output").exists() + + +def test_javascript_tree_filter_continues_after_non_regular_entries( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Symlinks and gitlinks cannot hide later regular lock inputs.""" + tree_output = ( + b"120000 blob " + (b"1" * 40) + b"\tsymlinked-lock\0" + b"160000 commit " + (b"2" * 40) + b"\tvendored-module\0" + b"100644 blob " + (b"3" * 40) + b"\tpackage.json\0" + ) + monkeypatch.setattr( + javascript_materializer, + "_git", + lambda *_args: tree_output, + ) + + assert javascript_materializer._regular_base_paths(tmp_path, "a" * 40) == { + "package.json" + } + + +def test_npm_project_without_packages_map_keeps_root_inputs( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Legacy npm locks without a packages map still preserve trusted root inputs.""" + regular_paths = {"package.json", "package-lock.json"} + lock_content = json.dumps({"name": "legacy", "lockfileVersion": 1}).encode() + monkeypatch.setattr( + javascript_materializer, + "_regular_base_paths", + lambda *_args: regular_paths, + ) + + def fake_git(_repo_root: Path, _command: str, object_spec: str) -> bytes: + if object_spec.endswith(":package.json"): + return b'{"name":"legacy"}' + if object_spec.endswith(":package-lock.json"): + return lock_content + raise AssertionError(f"unexpected git object: {object_spec}") + + monkeypatch.setattr(javascript_materializer, "_git", fake_git) + + projects = javascript_materializer.base_npm_projects(tmp_path, "a" * 40) + + assert projects == [ + ( + "package-lock.json", + "npm", + { + "package.json": b'{"name":"legacy"}', + "package-lock.json": lock_content, + }, + ) + ] + + +def test_npm_workspace_scan_iterates_missing_and_regular_manifests( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Missing workspace manifests are skipped without hiding later regular ones.""" + regular_paths = { + "package.json", + "package-lock.json", + "packages/alpha/package.json", + "packages/beta/package.json", + } + lock_content = json.dumps( + { + "name": "workspace-root", + "lockfileVersion": 3, + "packages": { + "": {"name": "workspace-root"}, + "packages/aaa-missing": {"name": "missing"}, + "packages/alpha": {"name": "alpha"}, + "packages/beta": {"name": "beta"}, + }, + } + ).encode() + blobs = { + "package.json": b'{"name":"workspace-root"}', + "package-lock.json": lock_content, + "packages/alpha/package.json": b'{"name":"alpha"}', + "packages/beta/package.json": b'{"name":"beta"}', + } + monkeypatch.setattr( + javascript_materializer, + "_regular_base_paths", + lambda *_args: regular_paths, + ) + + def fake_git(_repo_root: Path, _command: str, object_spec: str) -> bytes: + return blobs[object_spec.split(":", 1)[1]] + + monkeypatch.setattr(javascript_materializer, "_git", fake_git) + + projects = javascript_materializer.base_npm_projects(tmp_path, "a" * 40) + + assert len(projects) == 1 + assert "packages/aaa-missing/package.json" not in projects[0][2] + assert projects[0][2]["packages/alpha/package.json"] == b'{"name":"alpha"}' + assert projects[0][2]["packages/beta/package.json"] == b'{"name":"beta"}' diff --git a/tests/test_coverage_native_fuzz_lock_boundary.py b/tests/test_coverage_native_fuzz_lock_boundary.py new file mode 100644 index 000000000..36f1b3d77 --- /dev/null +++ b/tests/test_coverage_native_fuzz_lock_boundary.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import subprocess +from pathlib import Path + +from scripts.ci import materialize_base_python_requirements as materializer + + +def _git(repo: Path, *args: str) -> str: + """Run one deterministic Git command inside a temporary fixture repository.""" + + return subprocess.run( + ["git", "-C", str(repo), *args], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + + +def _hashed_requirement(package_name: str, digest_character: str) -> str: + """Create one syntactically hash-pinned requirement fixture line.""" + + return ( + f"{package_name}==1.0.0 --hash=sha256:" + f"{digest_character * 64}\n" + ) + + +def test_generic_coverage_excludes_only_the_exact_native_atheris_lock( + tmp_path: Path, +) -> None: + """Coverage retains test dependencies but never installs the Atheris toolchain.""" + + repo = tmp_path / "repository" + repo.mkdir() + _git(repo, "init") + _git(repo, "config", "user.name", "Coverage Boundary Test") + _git(repo, "config", "user.email", "coverage-boundary@example.invalid") + + fuzz_directory = repo / "fuzz" + fuzz_directory.mkdir() + (fuzz_directory / "requirements-atheris.txt").write_text( + _hashed_requirement("atheris", "a"), + encoding="utf-8", + ) + (fuzz_directory / "requirements-property.txt").write_text( + _hashed_requirement("hypothesis", "b"), + encoding="utf-8", + ) + + service_directory = repo / "services" / "example_service" + service_directory.mkdir(parents=True) + (service_directory / "requirements-fuzz-regression.txt").write_text( + _hashed_requirement("pytest", "c"), + encoding="utf-8", + ) + + _git(repo, "add", ".") + _git(repo, "commit", "-m", "base dependency roles") + base_sha = _git(repo, "rev-parse", "HEAD") + + output_directory = tmp_path / "materialized" + manifest = materializer.materialize(repo, base_sha, output_directory) + + assert [entry["source"] for entry in manifest] == [ + "fuzz/requirements-property.txt", + "services/example_service/requirements-fuzz-regression.txt", + ] + assert "requirements-atheris.txt" not in ( + output_directory / "manifest.json" + ).read_text(encoding="utf-8") + + +def test_native_fuzz_engine_classifier_uses_exact_file_names() -> None: + """Role classification cannot expand through substrings or directory names.""" + + assert materializer._is_native_fuzz_engine_lock_name( + "requirements-atheris.txt" + ) + assert not materializer._is_native_fuzz_engine_lock_name( + "requirements-atheris-regression.txt" + ) + assert not materializer._is_native_fuzz_engine_lock_name( + "requirements-property.txt" + ) diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 565ea4b9a..583a4a6a4 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -2729,3 +2729,15 @@ def test_r_package_load_deferral_requires_current_head_r_cmd_check(): assert ( "if (!is.na(pkg) && !requireNamespace(pkg, quietly = TRUE))" not in workflow ) + + +def test_opencode_coverage_image_provisions_compatible_llvm_tools(): + """Keep Rust coverage independent of a rustup-managed toolchain.""" + workflow = Path( + ".github/workflows/opencode-review-dispatch.yml" + ).read_text(encoding="utf-8") + + assert " llvm-19 " + chr(92) in workflow + assert "ENV LLVM_COV=/usr/bin/llvm-cov-19" in workflow + assert "ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19" in workflow + assert 'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"' in workflow diff --git a/tests/test_sanitize_github_output_summary.py b/tests/test_sanitize_github_output_summary.py index 72e2c19ee..973b3879c 100644 --- a/tests/test_sanitize_github_output_summary.py +++ b/tests/test_sanitize_github_output_summary.py @@ -1,9 +1,8 @@ -import runpy import sys import pytest -from scripts.ci.sanitize_github_output_summary import sanitize_text +from scripts.ci.sanitize_github_output_summary import _entrypoint, sanitize_text def test_sanitizes_secret_like_coverage_summary_values_without_losing_result(): @@ -36,6 +35,31 @@ def test_sanitizes_url_credentials_without_secret_key_prefix(): assert sanitized == "postgresql://@db:5432/app\n" +def test_sanitizes_url_userinfo_without_password(): + """A username-only URL authority cannot leak through coverage evidence.""" + sanitized = sanitize_text("https://alice@example.invalid/artifact\n") + + assert sanitized == "https://@example.invalid/artifact\n" + assert "alice" not in sanitized + + +def test_sanitizes_mixed_credentials_before_truncating_at_secret_key(): + """Mixed URL, Authorization, and key-value secrets are all removed.""" + source = ( + "failure https://alice:url-secret@example.invalid/a.tgz " + "Authorization: Bearer bearer-secret TOKEN=token-secret trailing context\n" + ) + + sanitized = sanitize_text(source) + + assert "https://@example.invalid/a.tgz" in sanitized + assert "Authorization: Bearer " in sanitized + assert "TOKEN=" in sanitized + assert "url-secret" not in sanitized + assert "bearer-secret" not in sanitized + assert "token-secret" not in sanitized + + def test_cli_writes_sanitized_summary(tmp_path, monkeypatch): source = tmp_path / "coverage.md" destination = tmp_path / "coverage-output.md" @@ -51,7 +75,7 @@ def test_cli_writes_sanitized_summary(tmp_path, monkeypatch): ) with pytest.raises(SystemExit) as excinfo: - runpy.run_path("scripts/ci/sanitize_github_output_summary.py", run_name="__main__") + _entrypoint("__main__") assert excinfo.value.code == 0 assert destination.read_text(encoding="utf-8") == "DATABASE_URL=\n- Result: PASS\n" diff --git a/tests/test_strix_dependency_security_floor.py b/tests/test_strix_dependency_security_floor.py new file mode 100644 index 000000000..768841a2b --- /dev/null +++ b/tests/test_strix_dependency_security_floor.py @@ -0,0 +1,23 @@ +"""Contracts for the security-reviewed Strix dependency lock.""" + +from __future__ import annotations + +from pathlib import Path + + +def test_strix_requirements_pin_reviewed_security_versions() -> None: + """The canonical input pins versions that close the August 2026 advisories.""" + requirements = Path("requirements-strix-ci.txt").read_text(encoding="utf-8") + assert "aiohttp==3.14.3\n" in requirements + assert "cryptography==50.0.0\n" in requirements + assert "aiohttp==3.14.1\n" not in requirements + assert "cryptography==49.0.0\n" not in requirements + + +def test_strix_hash_lock_matches_the_canonical_security_pins() -> None: + """The generated lock retains the reviewed direct security pins.""" + lock = Path("requirements-strix-ci-hashes.txt").read_text(encoding="utf-8") + assert "aiohttp==3.14.3 \\\n" in lock + assert "cryptography==50.0.0 \\\n" in lock + assert "aiohttp==3.14.1 \\\n" not in lock + assert "cryptography==49.0.0 \\\n" not in lock diff --git a/tests/test_strix_model_utils_source_dirs.py b/tests/test_strix_model_utils_source_dirs.py new file mode 100644 index 000000000..0267a0817 --- /dev/null +++ b/tests/test_strix_model_utils_source_dirs.py @@ -0,0 +1,94 @@ +"""Regression tests for Strix source-directory input boundaries.""" + +from __future__ import annotations + +import os +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +HELPER = ROOT / "scripts" / "ci" / "strix_model_utils.sh" + + +def run_source(raw_value: str) -> subprocess.CompletedProcess[str]: + """Source the helper with one caller-controlled directory-list value.""" + + environment = os.environ.copy() + environment["STRIX_SOURCE_DIRS"] = raw_value + return subprocess.run( + [ + "bash", + "-c", + f'source "{HELPER}" || exit $?; printf "%s" "$STRIX_SOURCE_DIRS"', + ], + cwd=ROOT, + env=environment, + text=True, + capture_output=True, + check=False, + timeout=10, + ) + + +def test_direct_source_directories_are_normalized_and_readonly() -> None: + """Keep direct safe names, Unicode names, and first-occurrence order.""" + + completed = run_source(". src 데이터 backend src 데이터") + assert completed.returncode == 0, completed.stderr + assert completed.stdout == ". src 데이터 backend" + + reassignment = subprocess.run( + [ + "bash", + "-c", + ( + f'STRIX_SOURCE_DIRS="."; source "{HELPER}"; ' + 'STRIX_SOURCE_DIRS="../etc"' + ), + ], + cwd=ROOT, + text=True, + capture_output=True, + check=False, + timeout=10, + ) + assert reassignment.returncode != 0 + assert "readonly" in reassignment.stderr.lower() + + +def test_traversal_absolute_nested_glob_and_empty_values_fail_closed() -> None: + """Reject every path shape that can escape or broaden the scan root.""" + + unsafe_values = ( + "..", + "../etc", + "/etc", + "src/../etc", + "src/api", + "*", + "-rf", + " ", + "src\nbackend", + ) + for raw_value in unsafe_values: + completed = run_source(raw_value) + assert completed.returncode == 2, ( + raw_value, + completed.stdout, + completed.stderr, + ) + assert "STRIX_SOURCE_DIRS" in completed.stderr + + +def test_unsafe_punctuation_and_oversized_lists_fail_closed() -> None: + """Bound metacharacters, encoded size, and list cardinality.""" + + for raw_value in ("src;echo", "src$HOME", "src\\api", "src?"): + completed = run_source(raw_value) + assert completed.returncode == 2, raw_value + + oversized_entry = "a" * 256 + assert run_source(oversized_entry).returncode == 2 + + oversized_list = " ".join(f"dir{index}" for index in range(33)) + assert run_source(oversized_list).returncode == 2