From 070c2579ad811bb08cad3f2182d81acbc1fcc49c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 17 Jul 2026 12:30:16 +0900 Subject: [PATCH] fix(review): keep Python coverage offline --- .github/workflows/opencode-review.yml | 143 ++------------------- requirements-opencode-review-ci-hashes.txt | 2 + requirements-opencode-review-ci.txt | 1 + scripts/ci/safe_pytest_command.py | 3 - scripts/ci/test_strix_quick_gate.sh | 30 ++--- tests/test_opencode_agent_contract.py | 143 +++++---------------- tests/test_opencode_security_boundaries.py | 4 +- 7 files changed, 68 insertions(+), 258 deletions(-) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index b0ed081ec..7010b2620 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -865,117 +865,9 @@ jobs: | sort -u } - pyproject_has_dev_dependency_group() { - python3 -I - "$1" <<'PY' - import sys - import tomllib - - with open(sys.argv[1], "rb") as fh: - data = tomllib.load(fh) - raise SystemExit(0 if "dev" in data.get("dependency-groups", {}) else 1) - PY - } - - pyproject_has_dev_optional_extra() { - python3 -I - "$1" <<'PY' - import sys - import tomllib - - with open(sys.argv[1], "rb") as fh: - data = tomllib.load(fh) - optional = data.get("project", {}).get("optional-dependencies", {}) - raise SystemExit(0 if "dev" in optional else 1) - PY - } - - pyproject_has_no_selected_dependencies() { - python3 -I - "$1" "$2" <<'PY' - import sys - import tomllib - - with open(sys.argv[1], "rb") as fh: - data = tomllib.load(fh) - - selection = sys.argv[2] - project = data.get("project", {}) - dynamic = project.get("dynamic", []) - if not isinstance(dynamic, list): - raise SystemExit(2) - if "dependencies" in dynamic: - raise SystemExit(1) - - dependencies = project.get("dependencies", []) - if not isinstance(dependencies, list): - raise SystemExit(2) - selected = list(dependencies) - - if selection == "group-dev": - group = data.get("dependency-groups", {}).get("dev", []) - if not isinstance(group, list): - raise SystemExit(2) - selected.extend(group) - elif selection == "extra-dev": - if "optional-dependencies" in dynamic: - raise SystemExit(1) - extra = project.get("optional-dependencies", {}).get("dev", []) - if not isinstance(extra, list): - raise SystemExit(2) - selected.extend(extra) - elif selection != "runtime": - raise SystemExit(2) - - raise SystemExit(0 if not selected else 1) - PY - } - - run_python_uv_lock_check() { - local project_dir="$1" - if [ -f "${project_dir}/uv.lock" ]; then - run_and_capture "Python uv lockfile consistency (${project_dir})" \ - bash -c 'cd "$1" && uv lock --check' bash "$project_dir" - fi - } - - install_python_project_dependencies() { - if [ -f requirements.txt ]; then - run_and_capture "Python project dependencies (requirements.txt)" \ - uv run --no-project --no-build --with-requirements requirements.txt python -c 'import sys; print("binary-only requirements resolved with", sys.executable)' - fi - - while IFS= read -r project_dir; do - pyproject_file="${project_dir}/pyproject.toml" - if [ -f "$pyproject_file" ]; then - run_python_uv_lock_check "$project_dir" - if pyproject_has_dev_dependency_group "$pyproject_file"; then - dependency_selection="group-dev" - elif pyproject_has_dev_optional_extra "$pyproject_file"; then - dependency_selection="extra-dev" - else - dependency_selection="runtime" - fi - - if pyproject_has_no_selected_dependencies "$pyproject_file" "$dependency_selection"; then - run_and_capture "Python project dependencies (${project_dir})" \ - python3 -c 'print("No selected runtime/dev dependencies are declared; safe dependency materialization is not applicable.")' - elif [ "$dependency_selection" = "group-dev" ]; then - run_and_capture "Python project dependencies (${project_dir})" \ - uv sync --project "$project_dir" --group dev --no-build --no-install-project - elif [ "$dependency_selection" = "extra-dev" ]; then - run_and_capture "Python project dependencies (${project_dir})" \ - uv sync --project "$project_dir" --extra dev --no-build --no-install-project - else - run_and_capture "Python project dependencies (${project_dir})" \ - uv sync --project "$project_dir" --no-build --no-install-project - fi - if [ -f "${project_dir}/requirements.txt" ]; then - run_and_capture "Python project dependencies (${project_dir}/requirements.txt in uv env)" \ - bash -c 'cd "$1" && uv run --no-project --no-build --with-requirements requirements.txt python -c "import sys; print(\"binary-only requirements resolved with\", sys.executable)"' bash "$project_dir" - fi - elif [ "$project_dir" != "." ] && [ -f "${project_dir}/requirements.txt" ]; then - run_and_capture "Python project dependencies (${project_dir}/requirements.txt)" \ - bash -c 'cd "$1" && uv run --no-project --no-build --with-requirements requirements.txt python -c "import sys; print(\"binary-only requirements resolved with\", sys.executable)"' bash "$project_dir" - fi - done < <(tracked_python_projects_with_tests) + verify_trusted_python_test_toolchain() { + run_and_capture "Trusted offline Python test toolchain" \ + python3 -I -c 'import coverage, interrogate, pytest, pytest_cov; print("trusted offline Python test toolchain imports passed")' } configured_python_ci_test_commands() { @@ -999,22 +891,16 @@ jobs: --project-dir "$project_dir" \ --command-json "$configured_command_json" done <<<"$configured_commands_json" - elif [ -f "${project_dir}/pyproject.toml" ]; then - run_and_capture "Python coverage with missing-line report (${project_dir})" \ - bash -c 'cd "$1" && PYTHONPATH=. uv run --no-build --with coverage --with pytest coverage run -m pytest tests && uv run --no-build --with coverage coverage report --show-missing' bash "$project_dir" - elif [ -f "${project_dir}/requirements.txt" ]; then - run_and_capture "Python coverage with missing-line report (${project_dir})" \ - bash -c 'cd "$1" && PYTHONPATH=. uv run --no-build --with-requirements requirements.txt --with coverage --with pytest coverage run -m pytest tests && uv run --no-build --with-requirements requirements.txt --with coverage coverage report --show-missing' bash "$project_dir" else run_and_capture "Python coverage with missing-line report (${project_dir})" \ - bash -c 'cd "$1" && PYTHONPATH=. uv run --no-build --with coverage --with pytest coverage run -m pytest tests && uv run --no-build --with coverage coverage report --show-missing' bash "$project_dir" + bash -c 'cd "$1" && PYTHONPATH=. python3 -m coverage run -m pytest tests && python3 -m coverage report --show-missing' bash "$project_dir" fi done < <(tracked_python_projects_with_tests) if [ "$measured_projects" -eq 0 ]; then if has_tracked_files '*.py'; then run_and_capture "Python coverage with missing-line report" \ - bash -c 'PYTHONPATH=. uv run --no-build --with coverage --with pytest coverage run -m pytest && uv run --no-build --with coverage coverage report --show-missing' + bash -c 'PYTHONPATH=. python3 -m coverage run -m pytest && python3 -m coverage report --show-missing' elif python3 -I -c 'import pytest_cov' >/dev/null 2>&1; then run_and_capture "Python pytest-cov coverage" python3 -m pytest --cov=. --cov-report=term-missing else @@ -1132,16 +1018,8 @@ jobs: while IFS= read -r project_dir; do if [ -f "${project_dir}/tests/test_docstrings.py" ]; then measured_projects=1 - if [ -f "${project_dir}/pyproject.toml" ]; then - run_and_capture "Python docstring coverage (${project_dir})" \ - bash -c 'cd "$1" && PYTHONPATH=. uv run --no-build pytest tests/test_docstrings.py' bash "$project_dir" - elif [ -f "${project_dir}/requirements.txt" ]; then - run_and_capture "Python docstring coverage (${project_dir})" \ - bash -c 'cd "$1" && PYTHONPATH=. uv run --no-build --with-requirements requirements.txt --with pytest python -m pytest tests/test_docstrings.py' bash "$project_dir" - else - run_and_capture "Python docstring coverage (${project_dir})" \ - bash -c 'cd "$1" && PYTHONPATH=. python3 -m pytest tests/test_docstrings.py' bash "$project_dir" - fi + run_and_capture "Python docstring coverage (${project_dir})" \ + bash -c 'cd "$1" && PYTHONPATH=. python3 -m pytest tests/test_docstrings.py' bash "$project_dir" fi done < <(tracked_python_projects_with_tests) [ "$measured_projects" -eq 1 ] @@ -1569,6 +1447,7 @@ jobs: implementation_changed_files="$(mktemp /tmp/implementation-changed-files.XXXXXX)" changed_files_for_coverage >"$implementation_changed_files" + chmod 0444 "$implementation_changed_files" run_and_capture "Implementation completeness scan" \ python3 "$GITHUB_WORKSPACE/scripts/ci/implementation_completeness_scan.py" \ --repo-root . \ @@ -1579,7 +1458,11 @@ jobs: if has_changed_tracked_files '*.py'; then measured_any=1 - install_python_project_dependencies + # PR-selected dependency manifests are never resolved in the + # networkless execution phase. The trusted image supplies the + # pinned review toolchain; missing project imports fail in pytest + # with the exact dependency name instead of reaching the network. + verify_trusted_python_test_toolchain run_python_test_coverage if run_python_docstring_coverage; then diff --git a/requirements-opencode-review-ci-hashes.txt b/requirements-opencode-review-ci-hashes.txt index 5336764df..2846355f3 100644 --- a/requirements-opencode-review-ci-hashes.txt +++ b/requirements-opencode-review-ci-hashes.txt @@ -21,6 +21,8 @@ pygments==2.20.0 \ --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 pytest==9.1.1 \ --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c +pytest-cov==7.1.0 \ + --hash=sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678 tabulate==0.10.0 \ --hash=sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3 uv==0.11.25 \ diff --git a/requirements-opencode-review-ci.txt b/requirements-opencode-review-ci.txt index 17c63da3b..b73fe9833 100644 --- a/requirements-opencode-review-ci.txt +++ b/requirements-opencode-review-ci.txt @@ -1,4 +1,5 @@ coverage==7.14.3 interrogate==1.7.0 pytest==9.1.1 +pytest-cov==7.1.0 uv==0.11.25 diff --git a/scripts/ci/safe_pytest_command.py b/scripts/ci/safe_pytest_command.py index 4143c0831..a5b1c655c 100644 --- a/scripts/ci/safe_pytest_command.py +++ b/scripts/ci/safe_pytest_command.py @@ -15,7 +15,6 @@ RUN_LINE_RE = re.compile(r"\s*(?:-\s*)?run:\s*(.+?)\s*$") PYTEST_EXECUTABLES = frozenset({"pytest", "py.test"}) PYTHON_EXECUTABLES = frozenset({"python", "python3"}) -RUNNER_EXECUTABLES = frozenset({"uv", "poetry", "pipenv"}) def _basename(value: str) -> str: @@ -32,8 +31,6 @@ def _is_pytest_argv(argv: Sequence[str]) -> bool: return True if executable in PYTHON_EXECUTABLES: return len(argv) >= 3 and argv[1:3] == ["-m", "pytest"] - if executable in RUNNER_EXECUTABLES: - return len(argv) >= 3 and argv[1] == "run" and _is_pytest_argv(argv[2:]) if executable == "coverage": return len(argv) >= 4 and argv[1:4] == ["run", "-m", "pytest"] return False diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index b8123a3a9..aae63c5da 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -550,7 +550,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" '--pids-limit 2048' "opencode coverage isolates pull-request process ancestry and bounds process use" assert_file_contains "$workflow_file" '--cap-drop ALL' "opencode coverage drops container capabilities before executing pull-request code" assert_file_contains "$workflow_file" 'setpriv' "opencode coverage executes pull-request commands under the non-root source owner" - assert_file_contains "$workflow_file" 'python3 -I - "$1"' "opencode trusted metadata parsers ignore PR-controlled Python module shadowing" + assert_file_contains "$workflow_file" "python3 -I -c 'import coverage, interrogate, pytest, pytest_cov" "opencode trusted tool verification ignores PR-controlled Python module shadowing" assert_file_contains "$workflow_file" 'python3 -I "$GITHUB_WORKSPACE/scripts/ci/sanitize_github_output_summary.py"' "opencode trusted output sanitizer runs in isolated Python mode" assert_file_contains "$workflow_file" 'CARGO_HOME=/work/.opencode-sandbox-home/.cargo' "opencode Rust tooling stays in the low-privilege sandbox home" assert_file_contains "$REPO_ROOT/scripts/ci/pr_review_merge_scheduler.py" '"pr_head_ref":' "central scheduler repository_dispatch carries the PR head branch required by current-head code-scanning verification" @@ -870,9 +870,13 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" 'GITHUB_PATH=/dev/null' "PR-controlled coverage commands cannot extend later-step PATH" assert_file_contains "$workflow_file" 'GITHUB_OUTPUT=/dev/null' "PR-controlled coverage commands cannot forge trusted step outputs" assert_file_contains "$workflow_file" 'BASH_ENV=/dev/null' "PR-controlled coverage commands cannot persist shell startup hooks" - assert_file_contains "$workflow_file" 'UV_NO_BUILD: "1"' "coverage wheel-only policy is scoped to the dependency-consuming measure step" - assert_file_contains "$workflow_file" 'uv run --no-project --no-build --with-requirements' "requirements resolution rejects PR-controlled source builds" - assert_file_contains "$workflow_file" 'uv run --no-build --with coverage' "coverage resolution rejects PR-controlled source builds" + assert_file_contains "$workflow_file" 'UV_NO_BUILD: "1"' "coverage preserves the no-build policy for any repository-configured uv test command" + assert_file_not_contains "$workflow_file" 'uv sync --project' "networkless coverage never resolves PR-selected pyproject dependencies" + assert_file_not_contains "$workflow_file" 'uv run --no-project' "networkless coverage never resolves PR-selected requirements files" + assert_file_not_contains "$workflow_file" 'uv run --no-build' "networkless coverage uses the trusted preinstalled Python toolchain directly" + assert_file_contains "$workflow_file" 'chmod 0444 "$implementation_changed_files"' "the sandbox identity can read but cannot rewrite the root-generated changed-file list" + assert_file_contains "$workflow_file" "verify_trusted_python_test_toolchain()" "coverage verifies all pinned Python review tools before executing PR tests" + assert_file_contains "$workflow_file" "import coverage, interrogate, pytest, pytest_cov" "the trusted image supplies the complete pinned Python review toolchain" assert_file_contains "$workflow_file" 'ref: ${{ steps.trusted_source.outputs.ref }}' "OpenCode review checks out validated central trusted scripts for same-head validation" assert_file_contains "$workflow_file" 'COVERAGE_EVIDENCE_RESULT: ${{ needs.coverage-evidence.result || '\''skipped'\'' }}' "opencode approval receives the coverage-evidence job conclusion" assert_file_contains "$workflow_file" 'PR_BASE_SHA: ${{ needs.validate-pr-metadata.outputs.base_sha }}' "coverage evidence receives the live validated PR base SHA for changed-file scoped measurement" @@ -888,7 +892,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" "npm ci --ignore-scripts" "coverage dependency installation suppresses npm lifecycle hooks" assert_file_contains "$workflow_file" "pnpm install --frozen-lockfile --ignore-scripts" "coverage dependency installation suppresses pnpm lifecycle hooks" assert_file_contains "$workflow_file" "yarn install --immutable --mode=skip-builds" "coverage dependency installation suppresses Yarn build hooks" - assert_file_contains "$workflow_file" "--no-build --no-install-project" "coverage dependency installation refuses PR-controlled Python build backends" + assert_file_contains "$workflow_file" "PR-selected dependency manifests are never resolved" "coverage refuses PR-controlled Python dependency resolution entirely" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'STRIX_EXECUTABLE_PATH=%s' "Strix workflow captures the pinned installation executable before scanning" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'STRIX_EXECUTABLE_SHA256=%s' "Strix workflow pins the installed executable digest before scanning" assert_file_contains "$REPO_ROOT/.github/workflows/strix.yml" 'STRIX_EXECUTABLE_ROOT=%s' "Strix workflow pins the installed executable root before scanning" @@ -948,21 +952,15 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" "workspace.metadata.opencode.coverage.minimum_lines" "opencode coverage evidence supports virtual-workspace Rust coverage baselines" assert_file_contains "$workflow_file" "scripts/ci/rust_coverage_threshold.py" "opencode coverage evidence uses the tested trusted Rust threshold parser" assert_file_contains "$workflow_file" '--fail-under-lines "$threshold"' "opencode coverage evidence enforces the resolved Rust line coverage threshold" - assert_file_contains "$workflow_file" "Python project dependencies (requirements.txt)" "opencode coverage evidence records repository Python dependency installation" - assert_file_contains "$workflow_file" "uv run --no-project --no-build --with-requirements requirements.txt" "opencode coverage evidence resolves wheel-only repository Python requirements before pytest" assert_file_contains "$workflow_file" "'requirements.txt' '*/requirements.txt'" "opencode coverage evidence discovers nested requirements-only Python test projects" - assert_file_contains "$workflow_file" "Python project dependencies (\${project_dir}/requirements.txt)" "opencode coverage evidence installs nested requirements-only Python project dependencies" - assert_file_contains "$workflow_file" "Python uv lockfile consistency (\${project_dir})" "opencode coverage evidence logs uv lockfile drift before installing uv-managed Python dependencies" - assert_file_contains "$workflow_file" "uv lock --check" "opencode coverage evidence rejects stale uv lockfiles before pytest" - assert_file_contains "$workflow_file" "uv sync --project" "opencode coverage evidence installs uv-managed Python project dependencies before pytest" - assert_file_contains "$workflow_file" 'cd "$1" && uv run --no-project --no-build --with-requirements requirements.txt' "opencode coverage evidence resolves requirements without executing a PR project backend" - assert_file_contains "$workflow_file" "--extra dev" "opencode coverage evidence installs pyproject optional dev extras when repositories do not use dependency-groups" assert_file_contains "$workflow_file" "configured_python_ci_test_commands()" "opencode coverage evidence prefers repository-configured CI pytest commands before falling back to the full tests tree" assert_file_contains "$workflow_file" 'safe_pytest_command.py" discover' "opencode coverage evidence discovers default CI workflow pytest commands through the trusted shell-free parser" + assert_file_not_contains "$REPO_ROOT/scripts/ci/safe_pytest_command.py" "RUNNER_EXECUTABLES" "configured pytest evidence cannot invoke uv, poetry, or pipenv dependency resolution" assert_file_contains "$workflow_file" "Python configured CI test suite" "opencode coverage evidence labels repository-configured pytest evidence separately" - assert_file_contains "$workflow_file" 'cd "$1" && PYTHONPATH=. uv run --no-build pytest tests' "opencode coverage evidence runs uv-managed Python project tests without source builds" - assert_file_contains "$workflow_file" 'cd "$1" && PYTHONPATH=. uv run --no-build --with-requirements requirements.txt --with coverage --with pytest coverage run -m pytest tests' "opencode coverage evidence runs requirements-only Python project coverage without source builds" - assert_file_contains "$workflow_file" 'cd "$1" && PYTHONPATH=. uv run --no-build --with-requirements requirements.txt --with pytest python -m pytest tests/test_docstrings.py' "opencode coverage evidence runs requirements-only Python docstring tests without source builds" + assert_file_contains "$workflow_file" 'cd "$1" && PYTHONPATH=. python3 -m coverage run -m pytest tests' "opencode coverage runs Python tests with the trusted preinstalled toolchain" + assert_file_contains "$workflow_file" 'python3 -m coverage report --show-missing' "opencode coverage preserves the missing-line report with the trusted toolchain" + assert_file_contains "$workflow_file" 'cd "$1" && PYTHONPATH=. python3 -m pytest tests/test_docstrings.py' "opencode docstring tests use the trusted preinstalled pytest" + assert_file_contains "$workflow_file" "missing project imports fail in pytest" "unavailable project dependencies fail closed with their import error" assert_file_contains "$workflow_file" "JavaScript/TypeScript dependencies (npm ci, lifecycle hooks disabled)" "opencode coverage evidence installs npm workspace dependencies without lifecycle hooks before JS coverage" assert_file_contains "$workflow_file" "coverage/coverage-summary.json" "opencode coverage evidence reads JS coverage summaries instead of trusting test exit codes" assert_file_contains "$workflow_file" "coverage/coverage-final.json" "opencode coverage evidence supports Vitest Istanbul final coverage files" diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index a533580fb..c8eb1d343 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -3,7 +3,6 @@ import re import shutil import subprocess -import sys import textwrap from pathlib import Path @@ -367,7 +366,9 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert "git -c core.quotePath=false ls-files" not in measure_step assert 'setpriv \\\n --reuid "$OPENCODE_SANDBOX_UID"' in measure_step assert 'pkill -KILL -u "$OPENCODE_SANDBOX_UID"' in measure_step - assert 'python3 -I - "$1"' in measure_step + assert 'chmod 0444 "$implementation_changed_files"' in measure_step + assert "verify_trusted_python_test_toolchain()" in measure_step + assert "import coverage, interrogate, pytest, pytest_cov" in measure_step assert "python3 -I -c 'import pytest_cov'" in measure_step assert ( 'python3 -I "$GITHUB_WORKSPACE/scripts/ci/sanitize_github_output_summary.py"' @@ -401,13 +402,18 @@ def test_opencode_target_coverage_materializes_only_after_authorized_dispatch(): assert "workspace.metadata.opencode.coverage.minimum_lines" in measure_step assert "scripts/ci/rust_coverage_threshold.py" in measure_step assert '--fail-under-lines "$threshold"' in measure_step - assert "run_python_uv_lock_check()" in measure_step - assert "pyproject_has_no_selected_dependencies()" in measure_step - assert "Python uv lockfile consistency (${project_dir})" in measure_step - assert "uv lock --check" in measure_step - assert measure_step.index( - 'run_python_uv_lock_check "$project_dir"' - ) < measure_step.index('uv sync --project "$project_dir" --group dev') + assert "uv sync --project" not in measure_step + assert "uv run --no-project" not in measure_step + assert "uv run --no-build" not in measure_step + assert "python3 -m coverage run -m pytest tests" in measure_step + trusted_requirements = Path( + "requirements-opencode-review-ci-hashes.txt" + ).read_text(encoding="utf-8") + assert "pytest-cov==7.1.0" in trusted_requirements + assert ( + "a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678" + in trusted_requirements + ) target_start = workflow.index(" opencode-review-target:\n") target_job = workflow[target_start:] @@ -487,101 +493,22 @@ def test_opencode_model_exhaustion_retry_stays_owned_by_central_scheduler(): assert "contents: write" not in workflow -def test_opencode_empty_pyproject_dependency_probe_is_fail_closed(tmp_path): - """Skip only declaratively empty dependency sets without running build hooks.""" +def test_opencode_python_coverage_never_resolves_pr_dependency_manifests(): + """Use only the trusted image toolchain during networkless PR execution.""" workflow = Path(".github/workflows/opencode-review.yml").read_text(encoding="utf-8") - function = workflow.split( - " pyproject_has_no_selected_dependencies() {\n", 1 - )[1].split("\n PY\n }", 1)[0] - probe = textwrap.dedent(function.split("<<'PY'\n", 1)[1]) - pyproject = tmp_path / "pyproject.toml" - - def run(source: str, selection: str) -> subprocess.CompletedProcess[str]: - pyproject.write_text(textwrap.dedent(source), encoding="utf-8") - return subprocess.run( - [sys.executable, "-", str(pyproject), selection], - input=probe, - capture_output=True, - text=True, - check=False, - ) + measure = workflow.split( + " - name: Measure test and docstring evidence\n", 1 + )[1].split("\n - name:", 1)[0] - assert ( - run( - """ - [project] - name = "empty" - dynamic = ["version"] - dependencies = [] - """, - "runtime", - ).returncode - == 0 - ) - assert ( - run( - """ - [project] - name = "runtime" - version = "1.0.0" - dependencies = ["pydantic>=2"] - """, - "runtime", - ).returncode - == 1 - ) - assert ( - run( - """ - [project] - name = "group" - version = "1.0.0" - dependencies = [] - - [dependency-groups] - dev = ["pytest>=8"] - """, - "group-dev", - ).returncode - == 1 - ) - assert ( - run( - """ - [project] - name = "dynamic" - version = "1.0.0" - dynamic = ["dependencies"] - """, - "runtime", - ).returncode - == 1 - ) - assert ( - run( - """ - [project] - name = "dynamic-extra" - version = "1.0.0" - dynamic = ["optional-dependencies"] - dependencies = [] - """, - "extra-dev", - ).returncode - == 1 - ) - assert ( - run( - """ - [project] - name = "malformed" - version = "1.0.0" - dependencies = "pytest" - """, - "runtime", - ).returncode - == 2 - ) + assert "verify_trusted_python_test_toolchain()" in measure + assert "PR-selected dependency manifests are never resolved" in measure + assert "missing project imports fail in pytest" in measure + assert "uv sync --project" not in measure + assert "uv run --no-project" not in measure + assert "uv run --no-build" not in measure + assert "python3 -m coverage run -m pytest tests" in measure + assert "python3 -m coverage report --show-missing" in measure + assert "python3 -m pytest tests/test_docstrings.py" in measure def test_opencode_coverage_prefers_preinstalled_declared_pnpm_before_npm(): @@ -1650,12 +1577,12 @@ def test_opencode_privileged_review_security_boundaries_are_fail_closed(): assert measure.count("GITHUB_OUTPUT=/dev/null") == 2 assert measure.count("GITHUB_STEP_SUMMARY=/dev/null") == 2 assert measure.count("BASH_ENV=/dev/null") == 2 - assert "uv run --no-project --no-build --with-requirements" in measure - assert "uv run --no-build --with coverage" in measure - assert ( - 'uv sync --project "$project_dir" --group dev --no-build --no-install-project' - in coverage_job - ) + assert "uv sync --project" not in measure + assert "uv run --no-project" not in measure + assert "uv run --no-build" not in measure + assert "Trusted offline Python test toolchain" in measure + assert "python3 -m coverage run -m pytest tests" in measure + assert 'chmod 0444 "$implementation_changed_files"' in measure assert "npm ci --ignore-scripts" in coverage_job assert "pnpm install --frozen-lockfile --ignore-scripts" in coverage_job assert "yarn install --immutable --mode=skip-builds" in coverage_job diff --git a/tests/test_opencode_security_boundaries.py b/tests/test_opencode_security_boundaries.py index b3fde6460..a194f0e68 100644 --- a/tests/test_opencode_security_boundaries.py +++ b/tests/test_opencode_security_boundaries.py @@ -135,7 +135,6 @@ def test_sensitive_log_redaction_handles_lists_empty_input_and_cli(monkeypatch: [ ("pytest -q tests", ["pytest", "-q", "tests"]), ("python3 -m pytest tests/unit", ["python3", "-m", "pytest", "tests/unit"]), - ("uv run pytest -q", ["uv", "run", "pytest", "-q"]), ("coverage run -m pytest tests", ["coverage", "run", "-m", "pytest", "tests"]), ], ) @@ -156,6 +155,9 @@ def test_safe_pytest_argv_classifier_rejects_empty_argv() -> None: "pytest && curl https://attacker.invalid", "bash -lc pytest", "curl pytest", + "uv run pytest -q", + "poetry run pytest -q", + "pipenv run pytest -q", "pytest `id`", "pytest $(id)", "pytest 'unterminated",