From c60626fc4b6cb1f0fcf4c589d2573a4340a024fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:53:48 +0000 Subject: [PATCH 1/7] test(release): prove dependency security regressions Stage: prefix7 Source: dd1b4121944f247c4d5dff0903902e2721d8c1a6 Ordinal: 1 Manifest: 6c3d344ecb69beca59231c3c49ae05dba50790fd253db87f37122028de1dfa2a Executor: bd639eb85d78f5a3d3438893297b31f5b3a93e88 Run: 39949294200 Attempt: 1 --- openspec/CHANGE_ORDER.md | 10 +- .../.openspec.yaml | 2 + .../CHANGE_VALIDATION.md | 8 + .../TDD_EVIDENCE.md | 88 ++++ .../design.md | 38 ++ .../proposal.md | 56 +++ .../requirements-evidence.yaml | 393 ++++++++++++++++++ .../requirements-proof/independent-review.md | 97 +++++ .../requirements-proof/review-evidence.json | 9 + .../specs/isolated-scanner-runtime/spec.md | 78 ++++ .../isolate-semgrep-security-runtime/tasks.md | 13 + tests/integration/test_distribution_build.py | 28 ++ .../unit/scripts/test_security_audit_gate.py | 74 ++++ .../test_frozen_dependency_artifacts.py | 225 ++++++++++ .../test_frozen_gitpython_compatibility.py | 81 ++++ .../security/test_frozen_jwt_compatibility.py | 84 ++++ .../test_release_dependency_selection.py | 38 ++ .../test_release_promotion_security_gates.py | 49 ++- 18 files changed, 1359 insertions(+), 12 deletions(-) create mode 100644 openspec/changes/isolate-semgrep-security-runtime/.openspec.yaml create mode 100644 openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md create mode 100644 openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md create mode 100644 openspec/changes/isolate-semgrep-security-runtime/design.md create mode 100644 openspec/changes/isolate-semgrep-security-runtime/proposal.md create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/independent-review.md create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json create mode 100644 openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md create mode 100644 openspec/changes/isolate-semgrep-security-runtime/tasks.md create mode 100644 tests/integration/test_distribution_build.py create mode 100644 tests/unit/security/test_frozen_dependency_artifacts.py create mode 100644 tests/unit/security/test_frozen_gitpython_compatibility.py create mode 100644 tests/unit/security/test_frozen_jwt_compatibility.py create mode 100644 tests/unit/security/test_release_dependency_selection.py diff --git a/openspec/CHANGE_ORDER.md b/openspec/CHANGE_ORDER.md index e192a50f..a4ad41f6 100644 --- a/openspec/CHANGE_ORDER.md +++ b/openspec/CHANGE_ORDER.md @@ -8,7 +8,7 @@ active changes should be implemented. | Bucket | Count | Location | |---|---:|---| -| **Active-tree entries** | 35 | [`openspec/changes/`](changes/) | +| **Active-tree entries** | 36 | [`openspec/changes/`](changes/) | | **Parking-lot entries** | 21 | [`openspec/parking-lot/`](parking-lot/) | | **Archived** | 120 | [`openspec/changes/archive/`](changes/archive/) | @@ -307,3 +307,11 @@ repo root. Do not move folders manually. - [`INTEGRATION.md`](INTEGRATION.md) - cross-change ownership contract - [`config.yaml`](config.yaml) - repo-wide OpenSpec rules and context - [`specfact-cli-modules/openspec/CHANGE_ORDER.md`](https://github.com/nold-ai/specfact-cli-modules/blob/main/openspec/CHANGE_ORDER.md) - module-side companion plan + +## Main dependency-only release phase + +`isolate-semgrep-security-runtime` prepares issue #780 under parent #747 on +`hotfix/security-dependencies-0-55-11`, based on main 5262efb0. Its native +test-first proof precedes production changes. This scope excludes dev-only +admission and runtime-smoke infrastructure. Future issues #523 and #783 do not +block this bounded release. No merge, signing, or publication is authorized. diff --git a/openspec/changes/isolate-semgrep-security-runtime/.openspec.yaml b/openspec/changes/isolate-semgrep-security-runtime/.openspec.yaml new file mode 100644 index 00000000..7cc41bb5 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-22 diff --git a/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md b/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md new file mode 100644 index 00000000..4aec407d --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md @@ -0,0 +1,8 @@ +# Scope validation + +Checked 2026-10-22 Europe/Berlin against main 5262efb0 in the dedicated release +worktree. This phase implements issue #780 release preparation, with parent #747. +The coordinating agent verified Todo status, labels, project, and hierarchy. +The dev change was read as context. Its admission artifacts and dev-only CI +changes are excluded. Genuine main native RED is required before production edits. +No independently accepted mapping or passing evidence is claimed yet. diff --git a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md new file mode 100644 index 00000000..24d6395c --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md @@ -0,0 +1,88 @@ +# Test-first evidence + +## Local RED before production edits + +Captured on 2026-10-22 Europe/Berlin against main commit 5262efb0 in the +dedicated release worktree. The final exact mapping selects 34 tests. +Result: **19 failed, 15 passed, 0 errors, 0 skipped**. The unchanged frozen +main dependencies ran in a newly bootstrapped private Python 3.12 environment. +The baseline runtime version remains 0.55.4. + +Observed failures include the compromised emSign certificate fingerprint in the +hash-pinned certifi wheel, an outside GitPython submodule reaching the clone +boundary, and PyJWT inspection mutating caller options. The source distribution +build fails because modules/bundle-mapper is absent. The other failures establish +old tooling selections, old documentation selections, missing bounded JSON floor, +the missing approved MCP record, old Semgrep selection, and old release metadata. +The expiry and future-review cases fail on the missing register's different +diagnostic. Existing negative audit cases and hostile wheel controls pass. + +The exact frozen PyJWT probe also executes signed and padded token decoding. +No installed verifier PyJWT import supplies that compatibility evidence. + +Local command: + +```sh +. /private/tmp/specfact-release-20261022/red-owner/env.sh +python - <<'PYTEST' +import json +import subprocess +import sys +from pathlib import Path +root = Path("/private/tmp/specfact-release-20261022/red-owner") +selectors = json.loads((root / "selectors.json").read_text()) +raise SystemExit(subprocess.call([ + sys.executable, "-m", "pytest", "--override-ini", "addopts=", "-q", + f"--junitxml={root / 'mapped-local-red.xml'}", *selectors, +])) +PYTEST +``` + +Local artifacts: + +- `/private/tmp/specfact-release-20261022/red-owner/selectors.json` +- `/private/tmp/specfact-release-20261022/red-owner/mapped-local-red.log` +- `/private/tmp/specfact-release-20261022/red-owner/mapped-local-red.xml` +- JUnit SHA256 `1de2aacfd11fc90c4bf422e2824adadaa9aeeacbeef5297eaff28081e79defec` + +## Native planning and review status + +`openspec validate isolate-semgrep-security-runtime --strict` passes. +Ruff checks pass for all seven changed test files. +The released immutable fixture's native staged `planned` gate passes. +Its source mapping digest is `sha256:8e3941ca05ccf9bceaea351df6cc1e642508debfc4ab87ecdc7b6b9cc45e6820`. +The combined plan digest is `sha256:9ef1ef74652759ecb82f7af7c8fa2b19c1d36b0d0bf043e2ed9b1a764bae95b2`. +Independent AI technical review accepted this exact mapping and its 34 selectors. +The retained review is `requirements-proof/independent-review.md`, SHA256 +`a981d9f787bd7dab64650254f50ebaa74cef55b79af64441c41525d7ced9692e`. +The acceptance record identifies that reviewer and does not represent human authority. + +Native test-authored acceptance, normal local hooks, a test-first commit, and +retained native CI RED are still required before any production changes. +Local JUnit does not replace native CI provenance. Mapping, selected tests, +ancestor conftest files, and transitive Python imports must freeze after native RED. +No prior dev admission or local signing supplies native evidence. + +## Final verification still required + +After native RED, verify the refreshed official artifacts and both strict audit +lanes, actual Ruby compatibility and documentation builds, changelog scope, +published-runtime byte equality except authorized versions, strict signatures, +and all required quality and CI gates. Merge and publication remain unauthorized. + +## Test-first commit checks + +The normal pre-commit hooks passed, including native test-authored planning, +format, lint, workflow and Markdown checks, and enforced Code Review. +Code Review reported 0 errors, 45 warnings and 3 informational observations. +All 45 warnings and two observations point to unchanged existing test bodies. +Their retention is a narrow baseline exception for this dependency-only release, +not a waiver of a required check. The new observation concerns the frozen JWT +probe's 48-line install, subprocess and assertion sequence. That explicit +sequence keeps its authenticated delivery artifact and isolated interpreter +visible. No additional wrapper or shared mutable fixture is introduced solely +to reduce its line count. Independent mapping review verified this boundary. + +The exact local report and changed-line comparison are retained under +`/private/tmp/specfact-release-20261022/red-owner/test-only-code-review.json` and +`code-review-line-triage.json`. Native CI RED and final release gates remain pending. diff --git a/openspec/changes/isolate-semgrep-security-runtime/design.md b/openspec/changes/isolate-semgrep-security-runtime/design.md new file mode 100644 index 00000000..34950c05 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/design.md @@ -0,0 +1,38 @@ +# Main dependency-only release design + +Use the existing main runtime and release workflows. Refresh generated frozen +inputs through the repository tooling after authentic native RED evidence exists. +No application function boundary changes are required. + +Artifact tests install or inspect the exact hash-pinned delivery wheel independently +of the verifier's package versions. Certificate parsing uses trusted SSL over inert +wheel data. GitPython and PyJWT run isolated subprocess probes. Malformed wheel +controls demonstrate rejection of altered contents, identity, hashes, paths, +origins, and duplicate records. + +The existing audit gate reads an exact MCP register entry. Tests exercise both +advisory IDs, the inclusive expiry day, wrong versions, unrelated and mixed +advisories, skipped dependencies, expiry, and a future review date. No audit lane +is disabled. The official Semgrep 1.179.0 package continues to select MCP 1.29.0. + +The source-distribution test invokes the ordinary uv build path with the installed +frozen build backend and verifies exact bundled manifest bytes in the resulting +wheel. The later fix adds only the missing source inclusion. + +The full intended production touchpoints are pyproject.toml, uv.lock, +requirements/ci/locked.txt, ci/vulnerability-audit-exceptions.json, setup.py, +src/__init__.py, src/specfact_cli/__init__.py, CHANGELOG.md, docs/Gemfile, +docs/Gemfile.lock, and scripts/check_jekyll_dependencies.rb and .github/workflows/docs-dependency-checks.yml. +The Ruby probe runs against installed locked gems after the dependency update. +Its output is supplementary compatibility evidence, not a substitute for native +mapped Python evidence. + +Risk controls retain published runtime bytes and signed manifests, constrain the +resolver to reviewed upgrades, and keep the exception exact and expiring. A failed +build, widened package graph, or failing audit blocks finalization. Rollback is the +unmerged PR diff. No release side effect occurs during this work. + +Native mapped proof covers the Python artifact behavior, release selections, +source build, bounded audit decision, and version metadata. Real Ruby behavior +published-runtime equality, and changelog scope are separate final acceptance checks. Their output +must be retained before final review and is not implied by the Python mapping. diff --git a/openspec/changes/isolate-semgrep-security-runtime/proposal.md b/openspec/changes/isolate-semgrep-security-runtime/proposal.md new file mode 100644 index 00000000..ebfed1b0 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/proposal.md @@ -0,0 +1,56 @@ +# Change: Release official security dependencies on main + +## Why + +The published v0.55.4 runtime must receive the reviewed dependency repairs without +unreleased dev runtime changes. This is the main release phase of issue #780. +The owner authorized version 0.55.11 and an exact MCP exception through +2026-11-04 inclusive. The previous dev admission work supplies no native RED +proof for this main branch. + +## What Changes + +- Refresh official Python packages to certifi 2026.10.19, GitPython 3.2.0, + Hatchling 1.32.4, PyJWT 2.15.1, urllib3 2.8.0, virtualenv 21.11.0, + python-discovery 1.6.0, and Semgrep 1.179.0. Preserve MCP 1.29.0. +- Bound the existing advisory register to MCP 1.29.0 and its reviewed advisory, + with review date 2026-10-21 and expiry 2026-11-04 inclusive. +- Refresh documentation gems to jekyll-feed 0.18.0, + jekyll-redirect-from 0.17.0, and jekyll-relative-links 0.9.1. + Require JSON >=2.21.2,<3 and verify the installed gem behavior separately. +- Include the existing bundle-mapper source in the source distribution so the + standard source-to-wheel build preserves its existing manifest bytes. +- Synchronize the four version sources to 0.55.11 and record the release in the changelog. + +## Capabilities + +### New Capabilities + +- `isolated-scanner-runtime`: official dependency repair and bounded advisory treatment for the main release. + +## Impact + +The base is main commit 5262efb0. Runtime content and signed module assets must +match published v0.55.4 commit d579970565530c3fd7b98bad4de90cf874c2a99d, +except the authorized version strings. All existing main CI and local hooks +remain enforced. Both existing Python audit lanes remain strict. The dev-only +runtime-smoke dependency graph, late-cycle admission, and scanner forks are +outside this release. No local signing, merge, or publication is authorized. + +## Source Tracking + +- Source Repository: nold-ai/specfact-cli +- Issue URL: +- Parent: +- Status: Todo, checked by the coordinating agent on 2026-10-22. +- Future migration issues #523 and #783 are outside this release. + +## Acceptance Criteria + +- The exact frozen certificate wheel excludes the compromised emSign root. +- The exact GitPython wheel rejects an outside submodule path before cloning. +- The exact PyJWT wheel preserves caller options and expiry verification on reuse. +- The only waived advisory is the exact approved MCP finding inside its dates. +- A real offline source-to-wheel build includes the unchanged bundled manifest. +- Native CI retains genuine test-first failures before dependency or release edits. +- Required quality checks, independent review, strict signatures, and dependency audits pass before the PR is offered for merge. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml b/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml new file mode 100644 index 00000000..b593dbdd --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml @@ -0,0 +1,393 @@ +schema_version: '2' +requirements: + ? openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:exclude-compromised-certificate-authorities-from-delivered-trust + : rationale: Inspect the delivery artifact independently of installed verifier packages. + stakeholder_refs: &id001 + - nold-ai/specfact-cli#780 + touchpoints: &id002 + - id: touchpoint-1 + kind: config_file + locator: pyproject.toml + - id: touchpoint-2 + kind: config_file + locator: uv.lock + - id: touchpoint-3 + kind: config_file + locator: requirements/ci/locked.txt + - id: touchpoint-4 + kind: test + locator: tests/unit/security/test_frozen_dependency_artifacts.py + - id: touchpoint-5 + kind: test + locator: tests/unit/security/test_frozen_gitpython_compatibility.py + - id: touchpoint-6 + kind: test + locator: tests/unit/security/test_frozen_jwt_compatibility.py + - id: touchpoint-7 + kind: test + locator: tests/unit/security/test_release_dependency_selection.py + verification_cases: + - case_id: CERTIFI-COMPROMISED-ROOT-ABSENT + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Inspect the exact frozen wheel and parse its inert CA data. + observable: The trust store is nonempty and excludes the compromised certificate fingerprint. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_frozen_certifi_bundle_excludes_compromised_root + - case_id: CERTIFI-WRONG-HASH + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Reject downloaded bytes that differ from the pinned hash. + observable: The downloader raises a SHA256 mismatch. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_wrong_sha256 + - case_id: WHEEL-DUPLICATE-MEMBER + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Reject a malformed wheel before using its contents. + observable: The duplicate_member fixture is refused with its specific diagnostic. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[duplicate_member] + - case_id: WHEEL-DUPLICATE-RECORD + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Reject a malformed wheel before using its contents. + observable: The duplicate_record fixture is refused with its specific diagnostic. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[duplicate_record] + - case_id: WHEEL-TRAVERSAL + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Reject a malformed wheel before using its contents. + observable: The traversal fixture is refused with its specific diagnostic. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[traversal] + - case_id: WHEEL-EXTRA-MEMBER + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Reject a malformed wheel before using its contents. + observable: The extra_member fixture is refused with its specific diagnostic. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[extra_member] + - case_id: WHEEL-CORRUPT-PAYLOAD + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Reject a malformed wheel before using its contents. + observable: The corrupt_payload fixture is refused with its specific diagnostic. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[corrupt_payload] + - case_id: WHEEL-WRONG-IDENTITY + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Reject a malformed wheel before using its contents. + observable: The wrong_identity fixture is refused with its specific diagnostic. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[wrong_identity] + - case_id: WHEEL-ORIGIN-8 + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Reject an unapproved artifact URL before download. + observable: The origin or query validation rejects the URL. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[http] + - case_id: WHEEL-ORIGIN-9 + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Reject an unapproved artifact URL before download. + observable: The origin or query validation rejects the URL. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[other-host] + - case_id: WHEEL-ORIGIN-10 + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Reject an unapproved artifact URL before download. + observable: The origin or query validation rejects the URL. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[credentials] + - case_id: WHEEL-ORIGIN-11 + scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust + method: test + intent: Reject an unapproved artifact URL before download. + observable: The origin or query validation rejects the URL. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[query] + openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:reject-submodule-checkouts-outside-the-repository: + rationale: Inspect the delivery artifact independently of installed verifier packages. + stakeholder_refs: *id001 + touchpoints: *id002 + verification_cases: + - case_id: GITPYTHON-SUBMODULE-CONTAINMENT + scenario_id: reject-submodule-checkouts-outside-the-repository + method: test + intent: Attempt an outside submodule update with clone boundary observation. + observable: The path is rejected before clone and no outside directory is created. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_gitpython_compatibility.py::test_submodule_update_rejects_outside_checkout_before_clone + openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:preserve-jwt-verification-options-and-compatibility: + rationale: Inspect the delivery artifact independently of installed verifier packages. + stakeholder_refs: *id001 + touchpoints: *id002 + verification_cases: + - case_id: PYJWT-OPTIONS-AND-EXPIRY + scenario_id: preserve-jwt-verification-options-and-compatibility + method: test + intent: Exercise the frozen JWT artifact in an isolated process. + observable: The frozen artifact preserves inspection options, rejects expiry on reuse, and decodes signed + and padded tokens without mutating caller options. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_jwt_compatibility.py::test_inspection_preserves_options_and_expiry_on_reuse + openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:select-the-reviewed-compatible-tooling-graph: + rationale: Inspect the delivery artifact independently of installed verifier packages. + stakeholder_refs: *id001 + touchpoints: *id002 + verification_cases: + - case_id: HATCHLING-FROZEN-SELECTION + scenario_id: select-the-reviewed-compatible-tooling-graph + method: test + intent: Check reviewed hatchling selection and export parity. + observable: The official registry lock version and exported requirement both match the reviewed version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[hatchling-1.32.4] + - case_id: URLLIB3-FROZEN-SELECTION + scenario_id: select-the-reviewed-compatible-tooling-graph + method: test + intent: Check reviewed urllib3 selection and export parity. + observable: The official registry lock version and exported requirement both match the reviewed version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[urllib3-2.8.0] + - case_id: VIRTUALENV-FROZEN-SELECTION + scenario_id: select-the-reviewed-compatible-tooling-graph + method: test + intent: Check reviewed virtualenv selection and export parity. + observable: The official registry lock version and exported requirement both match the reviewed version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[virtualenv-21.11.0] + - case_id: PYTHON-DISCOVERY-FROZEN-SELECTION + scenario_id: select-the-reviewed-compatible-tooling-graph + method: test + intent: Check reviewed python-discovery selection and export parity. + observable: The official registry lock version and exported requirement both match the reviewed version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[python-discovery-1.6.0] + openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:bound-the-temporary-official-mcp-advisory-exception: + rationale: Keep both main audit lanes strict with one exact, expiring owner-approved exception. + stakeholder_refs: + - nold-ai/specfact-cli#780 + touchpoints: + - id: touchpoint-1 + kind: config_file + locator: pyproject.toml + - id: touchpoint-2 + kind: config_file + locator: uv.lock + - id: touchpoint-3 + kind: config_file + locator: requirements/ci/locked.txt + - id: touchpoint-4 + kind: config_file + locator: ci/vulnerability-audit-exceptions.json + - id: touchpoint-5 + kind: test + locator: tests/unit/security/test_release_promotion_security_gates.py + - id: touchpoint-6 + kind: test + locator: tests/unit/scripts/test_security_audit_gate.py + verification_cases: + - case_id: MCP-OFFICIAL-EXCEPTION-EXACT + scenario_id: bound-the-temporary-official-mcp-advisory-exception + method: test + intent: Bind the official scanner graph and exact advisory record. + observable: Only official Semgrep 1.179.0 and MCP 1.29.0 occur with the approved exact exception. + selector: + runner: pytest + node_id: tests/unit/security/test_release_promotion_security_gates.py::test_frozen_graph_uses_official_semgrep_with_bounded_mcp_exception + - case_id: MCP-EXACT-CVE + scenario_id: bound-the-temporary-official-mcp-advisory-exception + method: test + intent: Exercise the committed register through the existing strict audit gate. + observable: Only the approved package, version, and advisory inside the inclusive date window is waived. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[exact-cve] + - case_id: MCP-EXACT-GHSA + scenario_id: bound-the-temporary-official-mcp-advisory-exception + method: test + intent: Exercise the committed register through the existing strict audit gate. + observable: Only the approved package, version, and advisory inside the inclusive date window is waived. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[exact-ghsa] + - case_id: MCP-EXPIRY-DAY + scenario_id: bound-the-temporary-official-mcp-advisory-exception + method: test + intent: Exercise the committed register through the existing strict audit gate. + observable: Only the approved package, version, and advisory inside the inclusive date window is waived. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[expiry-day] + - case_id: MCP-OTHER-VERSION + scenario_id: bound-the-temporary-official-mcp-advisory-exception + method: test + intent: Exercise the committed register through the existing strict audit gate. + observable: Only the approved package, version, and advisory inside the inclusive date window is waived. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[other-version] + - case_id: MCP-UNRELATED-ADVISORY + scenario_id: bound-the-temporary-official-mcp-advisory-exception + method: test + intent: Exercise the committed register through the existing strict audit gate. + observable: Only the approved package, version, and advisory inside the inclusive date window is waived. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[unrelated-advisory] + - case_id: MCP-MIXED-ADVISORIES + scenario_id: bound-the-temporary-official-mcp-advisory-exception + method: test + intent: Exercise the committed register through the existing strict audit gate. + observable: Only the approved package, version, and advisory inside the inclusive date window is waived. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[mixed-advisories] + - case_id: MCP-SKIPPED + scenario_id: bound-the-temporary-official-mcp-advisory-exception + method: test + intent: Exercise the committed register through the existing strict audit gate. + observable: Only the approved package, version, and advisory inside the inclusive date window is waived. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[skipped] + - case_id: MCP-EXPIRED + scenario_id: bound-the-temporary-official-mcp-advisory-exception + method: test + intent: Exercise the committed register through the existing strict audit gate. + observable: Only the approved package, version, and advisory inside the inclusive date window is waived. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[expired] + - case_id: MCP-FUTURE-REVIEW + scenario_id: bound-the-temporary-official-mcp-advisory-exception + method: test + intent: Exercise the committed register through the existing strict audit gate. + observable: Only the approved package, version, and advisory inside the inclusive date window is waived. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[future-review] + ? openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:rebuild-the-source-distribution-with-its-bundled-manifest + : rationale: Exercise the actual standard source-to-wheel delivery path. + stakeholder_refs: &id003 + - nold-ai/specfact-cli#780 + touchpoints: &id004 + - id: touchpoint-1 + kind: config_file + locator: pyproject.toml + - id: touchpoint-2 + kind: test + locator: tests/integration/test_distribution_build.py + - id: touchpoint-3 + kind: config_file + locator: docs/Gemfile + - id: touchpoint-4 + kind: config_file + locator: docs/Gemfile.lock + - id: touchpoint-5 + kind: config_file + locator: scripts/check_jekyll_dependencies.rb + - id: touchpoint-6 + kind: ci_workflow + locator: .github/workflows/docs-dependency-checks.yml + - id: touchpoint-7 + kind: test + locator: tests/unit/security/test_release_dependency_selection.py + - id: touchpoint-8 + kind: config_file + locator: setup.py + - id: touchpoint-9 + kind: config_file + locator: src/__init__.py + - id: touchpoint-10 + kind: config_file + locator: src/specfact_cli/__init__.py + - id: touchpoint-11 + kind: config_file + locator: CHANGELOG.md + - id: touchpoint-12 + kind: test + locator: tests/unit/security/test_release_promotion_security_gates.py + verification_cases: + - case_id: SOURCE-DISTRIBUTION-BUNDLED-MANIFEST + scenario_id: rebuild-the-source-distribution-with-its-bundled-manifest + method: test + intent: Build sdist then wheel through offline PEP 517. + observable: Build succeeds and the wheel manifest bytes equal the existing repository manifest. + selector: + runner: pytest + node_id: tests/integration/test_distribution_build.py::test_source_distribution_rebuild_preserves_bundle_mapper + openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:select-the-reviewed-documentation-dependency-versions: + rationale: Exercise the actual standard source-to-wheel delivery path. + stakeholder_refs: *id003 + touchpoints: *id004 + verification_cases: + - case_id: JEKYLL-FEED-LOCKED + scenario_id: select-the-reviewed-documentation-dependency-versions + method: test + intent: Check the reviewed documentation plugin lock selection. + observable: The lock selects the reviewed official version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_documentation_plugins_are_locked[jekyll-feed-0.18.0] + - case_id: JEKYLL-REDIRECT-FROM-LOCKED + scenario_id: select-the-reviewed-documentation-dependency-versions + method: test + intent: Check the reviewed documentation plugin lock selection. + observable: The lock selects the reviewed official version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_documentation_plugins_are_locked[jekyll-redirect-from-0.17.0] + - case_id: JEKYLL-RELATIVE-LINKS-LOCKED + scenario_id: select-the-reviewed-documentation-dependency-versions + method: test + intent: Check the reviewed documentation plugin lock selection. + observable: The lock selects the reviewed official version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_documentation_plugins_are_locked[jekyll-relative-links-0.9.1] + - case_id: JSON-REVIEWED-FLOOR + scenario_id: select-the-reviewed-documentation-dependency-versions + method: test + intent: Check the reviewed JSON floor and major-version limit. + observable: The declaration requires >=2.21.2 and <3. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_documentation_json_has_reviewed_floor_and_major_bound + openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:publish-one-coherent-patch-version: + rationale: Exercise the actual standard source-to-wheel delivery path. + stakeholder_refs: *id003 + touchpoints: *id004 + verification_cases: + - case_id: RELEASE-0-55-11-VERSION-SOURCES + scenario_id: publish-one-coherent-patch-version + method: test + intent: Read all four version files as data without importing runtime modules. + observable: All canonical version sources declare 0.55.11. + selector: + runner: pytest + node_id: tests/unit/security/test_release_promotion_security_gates.py::test_patch_release_uses_next_version_in_all_sources diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/independent-review.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/independent-review.md new file mode 100644 index 00000000..ff250ba5 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/independent-review.md @@ -0,0 +1,97 @@ +# Independent technical mapping review + +TL;DR. ACCEPTED for technical scenario-to-selector mapping and readiness to record independent acceptance, author the native test-first commit, and obtain genuine native CI RED. There are no unresolved blocking mapping or test-correctness findings in the reviewed content. This verdict does not authorize production changes before native CI RED, waive any repository gates, or authorize signing, merge, or publication. + +Reviewer identity is independent AI reviewer gpt-6.1-sol, agent /root/release_mapping_review. The owner is a separate Astra agent. This is an AI technical acceptance, not human approval. Review date is 2026-10-22 Europe/Berlin. Confidence is High for the inspected mapping, selectors, and recorded local results. Confidence in future CI or release success is not established by this review. + +The review was read-only against /private/tmp/specfact-release-20261022/worktrees/hotfix/security-dependencies-0-55-11 at HEAD 5262efb0881e4fac7697d629b0530250342bc656. I inspected staged contents and confirmed the reviewed files matched their working copies. I ran no test suite or build, changed no worktree content, staged nothing, and made no commit. The coordinating agent supplied frozen staged tree 9e61234b9130a48d8c7fac8e603881ec199176a7. The independently read file SHA256 values below are the precise review boundary. + +## Acceptance binding + +The schema is version 2. The final specification declares eight requirement/scenario pairs. All eight native requirement identities have mapping entries. Each scenario_id matches its corresponding imported native requirement slug. The final native planner reports planned maturity with gate_decision pass and no source findings. That is structural planning evidence only. It does not already represent accepted, test-authored, RED, or verified maturity. + +I independently recomputed the lifecycle canonical digests from the staged YAML and plan JSON using the canonical typed encoding implemented by the native lifecycle module. The source mapping digest is the value to bind in the source review-evidence.json acceptance record. + +- Source acceptance mapping digest is sha256:8e3941ca05ccf9bceaea351df6cc1e642508debfc4ab87ecdc7b6b9cc45e6820. +- Aggregate mapping digest is sha256:32cf65041606a9c43af3cfd506405d74a871d4cc07c292e63de3238ae87cddcf. +- Aggregate plan digest is sha256:9ef1ef74652759ecb82f7af7c8fa2b19c1d36b0d0bf043e2ed9b1a764bae95b2. +- Native plan identity digest is sha256:678474bc2cf581c9bf424833312f572c8ce234a9b92f66e6b95612282707bc6a. + +There are 34 verification cases, 34 unique pytest selectors, and 34 cases in the native plan. The YAML and native-plan selector sets match exactly. Every selected case appears exactly once in mapped-local-red.xml. There are no missing or extra local XML cases, collection errors, or skips. + +## Actual case coverage + +| Native scenario | Cases | What the selected checks establish | +| --- | ---: | --- | +| Exclude compromised certificate authorities from delivered trust | 12 | Downloads the lock-selected certifi wheel from the approved exact host, checks the pinned hash and export agreement, validates its inert wheel inventory and RECORD, parses a nonempty CA bundle, excludes the specified DER fingerprint, and exercises wrong hash, six malformed-wheel controls, and four unapproved URL forms. | +| Reject submodule checkouts outside the repository | 1 | Installs the hash-pinned GitPython delivery wheel into a temporary target, runs it under -I -S, observes rejection before the mocked clone boundary, and checks no outside directory was created. | +| Preserve JWT verification options and compatibility | 1 | Installs the hash-pinned PyJWT delivery wheel into a temporary target and runs the options, expiry reuse, signed-token, padded-signature, and caller-options checks in the same isolated process. | +| Select the reviewed compatible tooling graph | 4 | Checks the reviewed Hatchling, urllib3, virtualenv, and python-discovery versions, official registry identities, and matching exported requirement versions. | +| Bound the temporary official MCP advisory exception | 10 | One case binds official Semgrep 1.179.0, MCP 1.29.0, the exact advisory register record and dates. Nine cases exercise the existing strict gate using the committed register for both advisory IDs, expiry day, other version, unrelated advisory, mixed advisories, skipped dependency, expiry, and future review date. | +| Rebuild the source distribution with its bundled manifest | 1 | Invokes ordinary offline uv build with the installed backend and verifies the bundled wheel manifest bytes equal the repository manifest. Recorded build diagnostics confirm this path builds the wheel from the source distribution. | +| Select the reviewed documentation dependency versions | 4 | Checks the three reviewed plugin lock versions and the JSON >=2.21.2,<3 declaration. These are dependency-selection checks, not executed gem compatibility coverage. | +| Publish one coherent patch version | 1 | Parses pyproject project.version and the version assignments in setup.py and both source init files to require 0.55.11. | + +The initially observed JWT compatibility test used verifier imports for signed/padded decoding. The owner corrected it before freeze by moving those checks into the isolated frozen-wheel probe and removing the standalone verifier-package test. I re-read the corrected staged probe and mapping. The owner also replaced version-substring checks with actual assignment extraction before freeze. These findings are resolved in the reviewed content. + +## Genuine local RED and its limits + +The exact selected local run is /private/tmp/specfact-release-20261022/red-owner/mapped-local-red.log and mapped-local-red.xml, timestamp 2026-10-22T17:51:18.921142+02:00. The XML records 34 tests, 19 failures, 15 passes, 0 errors, and 0 skips. I independently reconciled its case identities with the 34 native-plan selectors. + +I read the failure diagnostics rather than treating any nonzero exit as sufficient evidence. The certificate probe found the specified root fingerprint in the delivered CA set. GitPython reached the deliberately observed clone boundary for ../outside. PyJWT changed the caller options after inspection. The source build failed because the bundle-mapper forced include was absent from the generated source distribution. The reviewed tooling and documentation versions, release version, Semgrep selection, and missing exception produced the expected old-state declaration disagreements. + +The exception expiry/future-review cases currently fail on the missing expected invalid-register diagnostic because the committed register is empty. They do not prove that old gate code incorrectly waived an expired record. Four other negative exception controls and the malformed-artifact controls already pass. That is expected. This review does not require every selected case to fail and does not reclassify passing controls as new RED evidence. + +The older local-red.log and local-red.xml contain a broader 48-case run from before final portable parameter IDs and combined JWT coverage. They are historical diagnostics only and are not the final selector-bound local evidence accepted here. The initial rejected planner artifacts were superseded by the current passing planned artifacts. + +Local results are supplementary test-first evidence. They are not retained native CI RED and carry no GitHub run identity. The next required gate is genuine native CI execution of this accepted frozen plan before any production dependency, version, packaging, or release changes. No earlier dev admission, synthetic failure, alternate fork, or late RED mechanism substitutes for that gate. + +## Production boundary and supplementary checks + +The staged diff contains the selected OpenSpec artifacts, CHANGE_ORDER, and tests only. It contains no production implementation changes. The design and mapping enumerate the intended later production touchpoints. Their union covers pyproject.toml, uv.lock, requirements/ci/locked.txt, ci/vulnerability-audit-exceptions.json, setup.py, src/__init__.py, src/specfact_cli/__init__.py, CHANGELOG.md, docs/Gemfile, docs/Gemfile.lock, scripts/check_jekyll_dependencies.rb, and .github/workflows/docs-dependency-checks.yml. Existing main requirements-evidence and audit infrastructure is unchanged. + +Actual Ruby feed escaping, redirects, relative links, and JSON behavior are supplementary final compatibility evidence. Published-v0.55.4 runtime byte equality, signed asset equality, exact certifi/GitPython/PyJWT reviewed version selection, full frozen export/hash closure, strict signatures, both audit lanes, changelog scope, local hooks, and final CI checks also remain required. They are not implied by the 34 mapped selectors. The final specification and design avoid claiming native Python coverage for the Ruby probes, published payload comparison, or changelog semantics. + +The tests prove the selected behavior and declarations at their stated boundaries. They do not prove every vulnerability or every compatibility property in every upgraded dependency. GitPython uses installed verifier gitdb/smmap dependency roots while selecting the delivery GitPython code first. The build test uses the installed backend and therefore must execute later in the intended frozen build environment. These are explicit environment assumptions, not claims of fully self-contained dependency execution. + +The exception is exact MCP 1.29.0 for CVE-2026-59951 and GHSA-84m7-p3x7-pcfv, reviewed 2026-10-21 through inclusive expiry 2026-11-04. The tests pin these values. This review evaluates that contract and does not independently validate the external advisory facts or extend the exception window. + +## Remaining actions and failure controls + +One nonblocking formatting finding remains in the inspected index. git diff --cached --check reports a new blank line at EOF at spec.md line 79. The coordinator has explicitly taken this whitespace-only cleanup before hooks. Any resulting spec byte hash must be recorded separately; substantive spec, mapping, selector, fixture, or transitive-import changes require renewed review and appropriate fresh test-first evidence. Updating the acceptance identity record to cite this review is expected and does not imply permission to change selected test behavior. + +The acceptance record must identify this AI reviewer accurately and bind the source mapping digest above. Retain genuine native CI RED before production edits. Freeze mapped tests, selected mappings, ancestor conftest files, and transitive test imports through the RED-to-GREEN cycle. A selector drift or changed test helper can break proof continuity; mitigate it with exact recorded identity checks rather than a renamed test or regenerated narrative. An unreviewed dependency graph or changed runtime payload can violate the dependency-only boundary; mitigate it with the required final graph and byte comparisons. An invalid or widened advisory register can weaken auditing; keep both audit lanes strict and rerun the exact positive and negative controls. + +No runtime cost or performance claim was measured. The selected local run XML reports 1.012 seconds of suite time on this machine; that is not a CI duration forecast. Rollback before merge is removal of the unmerged release diff. No release effect occurred during this review. + +Test Behavior, Not Implementation shaped the decision to require signed/padding checks against the frozen delivery PyJWT rather than verifier imports. Prove It Works shaped the direct comparison of staged mapping digests and exact selected XML outcomes, including the concrete disagreement in each behavior failure. + +## Precise reviewed file identities + +The following SHA256 values were read directly from the Git index. They matched the working files at review time. Paths are relative to the reviewed worktree root. + +- `b9d8f369ba91041aef3707aa02e199381e70dfbb75d5ab6d35015ed0b48a27f2` `openspec/CHANGE_ORDER.md` +- `0ad492a63d87e00a8bc135b0a1a2b19270dbb9ffd997a69b89fd4ab72015a944` `openspec/changes/isolate-semgrep-security-runtime/.openspec.yaml` +- `27b886b35c4f802d4796d8b75c3f2aef9b554f70c27bf6f616e9f8ff93f9f93d` `openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md` +- `6ef7cc7a32b03505fb62a0f2215e560ed03cc73a98f2e1a8e99e5ce92fba75c3` `openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md` +- `0f19e601ced4855fc0ae8da78eb6605d1262a7fd05f5a39d0ffefd7001a8daac` `openspec/changes/isolate-semgrep-security-runtime/design.md` +- `87d33cfa09d1ad7ee89a7c4b45ab6b6d828b2b077f9f825a089c4ede622ac35c` `openspec/changes/isolate-semgrep-security-runtime/proposal.md` +- `815016cd54dee63ba1fc66836593004e4c917c942f10eb24462408aadd22a5d1` `openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml` +- `2ec960286a47415ce3f6f4cd275f699a138d8abcc4d45d3eeb18639b92fbec53` `openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md` +- `d0bec2f806fbd9fff48f5c40d7b54eeea1d7558868a09679988cf75084aca330` `openspec/changes/isolate-semgrep-security-runtime/tasks.md` +- `af24d9caa1259ecf9789440c516784ea01d4afb34acf8fe9ef377f5f2f46806b` `tests/integration/test_distribution_build.py` +- `d0b1fcb16185b1908b6abc53e0c68e7baf04ede2b395492fa4d4cbb37ed8c3b9` `tests/unit/scripts/test_security_audit_gate.py` +- `146f45786ccb74b581c36a1a626130c540631e45fa869433d5663e56202b4215` `tests/unit/security/test_frozen_dependency_artifacts.py` +- `527d78a6dd07e04e00fefa06632cfa320725f035c22d988e0a1741a455ece728` `tests/unit/security/test_frozen_gitpython_compatibility.py` +- `35b8e0d571bf50a30599ad1634b068d2d3d37e9a46ffd45fe6dfea03771cab16` `tests/unit/security/test_frozen_jwt_compatibility.py` +- `7e41674b9b6c2bfb210f50084125821e54c662b079d932be2ba2ebb787bf876f` `tests/unit/security/test_release_dependency_selection.py` +- `dcf6de107fea93357421daae229bbdd144d0e592d56b28c58b0429aa4ddd9531` `tests/unit/security/test_release_promotion_security_gates.py` +- `6ec0f7ee75694f3ba75a1c103713e92aaf162758c869c3d4db353c6f9725c6e6` `tests/conftest.py` +- `762543a00a6519647357e1e72d296cf64e9512f0885fd2563c4df9aec8f8f4d0` `scripts/security_audit_gate.py` + +Evidence file SHA256 values follow. Paths are relative to /private/tmp/specfact-release-20261022/red-owner. + +- `7999adc5cc6bd8d1f689c40e14f573268872babb64dcea5380e549355fc38dbc` `requirements-plan.json` +- `3a62f93ab41b10242e080a969cca36399143b7360fb3b2a611c753b9e6538bfe` `requirements-evidence.json` +- `5f7e4988c0b8a50b3601712b54c176e0d916c6e947fb34e903961f3ead64e684` `mapped-local-red.log` +- `1de2aacfd11fc90c4bf422e2824adadaa9aeeacbeef5297eaff28081e79defec` `mapped-local-red.xml` diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json new file mode 100644 index 00000000..f7d218bc --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json @@ -0,0 +1,9 @@ +{ + "schema_version": "1", + "decision": "accepted", + "reviewer_id": "Codex:/root/release_mapping_review (gpt-6.1-sol)", + "reviewer_role": "independent AI technical mapping reviewer", + "recorded_at": "2026-10-22T15:55:08.939596+00:00", + "reference": "https://github.com/nold-ai/specfact-cli/blob/hotfix/security-dependencies-0-55-11/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/independent-review.md", + "mapping_digest": "sha256:8e3941ca05ccf9bceaea351df6cc1e642508debfc4ab87ecdc7b6b9cc45e6820" +} diff --git a/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md b/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md new file mode 100644 index 00000000..bee733dd --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md @@ -0,0 +1,78 @@ +## ADDED Requirements + +### Requirement: Exclude compromised certificate authorities from delivered trust + +The main release SHALL satisfy this delivery contract using the reviewed official dependencies. + +#### Scenario: Exclude compromised certificate authorities from delivered trust + +- **WHEN** the exact frozen certifi wheel is inspected and its CA certificates are parsed +- **THEN** the trust store SHALL be nonempty and exclude emSign ECC Root CA - G3 DER SHA256 86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b +- **AND** malformed artifacts, invalid hashes, and unapproved origins SHALL be rejected. + +### Requirement: Reject submodule checkouts outside the repository + +The main release SHALL satisfy this delivery contract using the reviewed official dependencies. + +#### Scenario: Reject submodule checkouts outside the repository + +- **WHEN** the exact frozen GitPython wheel updates a submodule with path ../outside +- **THEN** it SHALL reject the path before invoking clone and SHALL create no outside directory. + +### Requirement: Preserve JWT verification options and compatibility + +The main release SHALL satisfy this delivery contract using the reviewed official dependencies. + +#### Scenario: Preserve JWT verification options and compatibility + +- **WHEN** the exact frozen PyJWT wheel inspects a token without signature verification and caller options are reused for verified decoding +- **THEN** inspection SHALL preserve caller options and expiry verification SHALL still reject an expired token +- **AND** signed decoding and supported signature padding SHALL remain compatible. + +### Requirement: Select the reviewed compatible tooling graph + +The main release SHALL satisfy this delivery contract using the reviewed official dependencies. + +#### Scenario: Select the reviewed compatible tooling graph + +- **WHEN** the release frozen graph is resolved +- **THEN** it SHALL select Hatchling 1.32.4, urllib3 2.8.0, virtualenv 21.11.0, and python-discovery 1.6.0 +- **AND** the lock and hashed delivery export SHALL agree. + +### Requirement: Bound the temporary official MCP advisory exception + +The main release SHALL satisfy this delivery contract using the reviewed official dependencies. + +#### Scenario: Bound the temporary official MCP advisory exception + +- **WHEN** official Semgrep 1.179.0 selects MCP 1.29.0 +- **THEN** only CVE-2026-59951 and its alias GHSA-84m7-p3x7-pcfv SHALL be waived from 2026-10-21 through 2026-11-04 inclusive +- **AND** other versions, unrelated or mixed advisories, skipped dependencies, expired records, and future review dates SHALL fail closed. + +### Requirement: Rebuild the source distribution with its bundled manifest + +The main release SHALL satisfy this delivery contract using the reviewed official dependencies. + +#### Scenario: Rebuild the source distribution with its bundled manifest + +- **WHEN** the ordinary offline PEP 517 source-distribution-to-wheel build runs +- **THEN** the build SHALL succeed and the wheel SHALL contain the original bundle-mapper manifest bytes. + +### Requirement: Select the reviewed documentation dependency versions + +The main release SHALL satisfy this delivery contract using the reviewed official dependencies. + +#### Scenario: Select the reviewed documentation dependency versions + +- **WHEN** the documentation dependencies are installed from the lock +- **THEN** jekyll-feed 0.18.0, jekyll-redirect-from 0.17.0, and jekyll-relative-links 0.9.1 SHALL be selected +- **AND** JSON SHALL be constrained to >=2.21.2,<3 + +### Requirement: Publish one coherent patch version + +The main release SHALL satisfy this delivery contract using the reviewed official dependencies. + +#### Scenario: Publish one coherent patch version + +- **WHEN** release metadata is read +- **THEN** all four canonical version sources SHALL declare 0.55.11. diff --git a/openspec/changes/isolate-semgrep-security-runtime/tasks.md b/openspec/changes/isolate-semgrep-security-runtime/tasks.md new file mode 100644 index 00000000..7e084877 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/tasks.md @@ -0,0 +1,13 @@ +# Main release tasks + +- [ ] Validate this focused scope and independently accept the scenario mapping. +- [ ] Commit test-authored content with all local hooks and retain native failing CI evidence. +- [ ] Freeze mappings, selected tests, ancestor conftest files, and transitive Python imports. +- [ ] Refresh the eight reviewed official Python packages and both frozen files. +- [ ] Add only the exact approved MCP advisory exception. +- [ ] Update documentation dependency declarations and lock, then execute real gem compatibility probes. +- [ ] Add the missing bundle-mapper source-distribution inclusion and synchronize version 0.55.11 with the changelog. +- [ ] Verify published runtime equality, strict module signatures, both audit lanes, and required local and CI gates. +- [ ] Complete independent final review and present the unmerged PR. +- [ ] Update the internal wiki mirror and rebuild its graph through the coordinating agent. +- [ ] Archive with the native OpenSpec command only after the release change is complete. diff --git a/tests/integration/test_distribution_build.py b/tests/integration/test_distribution_build.py new file mode 100644 index 00000000..f875a381 --- /dev/null +++ b/tests/integration/test_distribution_build.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +import shutil +import subprocess +import sys +import zipfile +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def test_source_distribution_rebuild_preserves_bundle_mapper(tmp_path: Path) -> None: + uv = shutil.which("uv") + assert uv is not None + build = subprocess.run( + [uv, "build", "--offline", "--no-build-isolation", "--python", sys.executable, "--out-dir", str(tmp_path)], + cwd=REPO_ROOT, + capture_output=True, + text=True, + timeout=120, + check=False, + ) + assert build.returncode == 0, build.stderr + wheel = next(tmp_path.glob("*.whl")) + with zipfile.ZipFile(wheel) as archive: + manifest = archive.read("specfact_cli/modules/bundle-mapper/module-package.yaml") + assert manifest == (REPO_ROOT / "modules/bundle-mapper/module-package.yaml").read_bytes() diff --git a/tests/unit/scripts/test_security_audit_gate.py b/tests/unit/scripts/test_security_audit_gate.py index bf7906de..19c08cdb 100644 --- a/tests/unit/scripts/test_security_audit_gate.py +++ b/tests/unit/scripts/test_security_audit_gate.py @@ -6,6 +6,8 @@ import json from datetime import date, timedelta from pathlib import Path +from types import ModuleType +from typing import NamedTuple from unittest.mock import MagicMock, patch import pytest @@ -171,3 +173,75 @@ def test_main_allows_only_a_matching_unexpired_reviewed_exception(gate_mod, tmp_ with patch.object(gate_mod.subprocess, "run", return_value=proc): assert gate_mod.main(["--exceptions", str(exceptions_path)]) == 0 assert "WAIVED" in capsys.readouterr().out + + +class McpExceptionScenario(NamedTuple): + version: str + advisory_ids: list[str] + skip_reason: str | None + today: str + expected: int + message: str + + +@pytest.mark.parametrize( + "scenario", + [ + pytest.param( + McpExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-10-21", 0, "WAIVED"), id="exact-cve" + ), + pytest.param( + McpExceptionScenario("1.29.0", ["GHSA-84m7-p3x7-pcfv"], None, "2026-10-21", 0, "WAIVED"), id="exact-ghsa" + ), + pytest.param( + McpExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-11-04", 0, "WAIVED"), id="expiry-day" + ), + pytest.param( + McpExceptionScenario("1.30.0", ["CVE-2026-59951"], None, "2026-10-21", 1, "FAIL:"), id="other-version" + ), + pytest.param( + McpExceptionScenario("1.29.0", ["UNRELATED-1"], None, "2026-10-21", 1, "FAIL:"), id="unrelated-advisory" + ), + pytest.param( + McpExceptionScenario("1.29.0", ["CVE-2026-59951", "UNRELATED-1"], None, "2026-10-21", 1, "FAIL:"), + id="mixed-advisories", + ), + pytest.param( + McpExceptionScenario( + "1.29.0", ["CVE-2026-59951"], "could not audit", "2026-10-21", 1, "skipped dependency" + ), + id="skipped", + ), + pytest.param( + McpExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-11-05", 1, "not currently valid"), + id="expired", + ), + pytest.param( + McpExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-10-20", 1, "not currently valid"), + id="future-review", + ), + ], +) +def test_committed_mcp_exception_is_exact_and_time_bounded( + gate_mod: ModuleType, + capsys: pytest.CaptureFixture[str], + scenario: McpExceptionScenario, +) -> None: + class AuditDate(date): + @classmethod + def today(cls) -> date: + return date.fromisoformat(scenario.today) + + dependency: dict[str, object] = { + "name": "mcp", + "version": scenario.version, + "vulns": [{"id": value} for value in scenario.advisory_ids], + } + if scenario.skip_reason: + dependency["skip_reason"] = scenario.skip_reason + proc = MagicMock(stdout=json.dumps({"dependencies": [dependency]}), stderr="", returncode=1) + exceptions = Path(__file__).resolve().parents[3] / "ci/vulnerability-audit-exceptions.json" + with patch.object(gate_mod, "date", AuditDate), patch.object(gate_mod.subprocess, "run", return_value=proc): + assert gate_mod.main(["--exceptions", str(exceptions)]) == scenario.expected + captured = capsys.readouterr() + assert scenario.message in captured.out + captured.err diff --git a/tests/unit/security/test_frozen_dependency_artifacts.py b/tests/unit/security/test_frozen_dependency_artifacts.py new file mode 100644 index 00000000..61eefb36 --- /dev/null +++ b/tests/unit/security/test_frozen_dependency_artifacts.py @@ -0,0 +1,225 @@ +from __future__ import annotations + +import base64 +import csv +import hashlib +import io +import re +import ssl +import time +import tomllib +import urllib.request +from dataclasses import dataclass +from email.message import Message +from email.parser import BytesParser +from pathlib import Path, PurePosixPath +from urllib.parse import urlsplit +from zipfile import ZipFile, ZipInfo + +import pytest + + +_MAX_DOWNLOAD = 150 * 1024 * 1024 +_MAX_EXPANDED = 512 * 1024 * 1024 +_BAD_CA = "86a1ecba089c4a8d3bbe2734c612ba341d813e043cf9e8a862cd5c57a36bbe6b" + + +@dataclass(frozen=True) +class WheelContents: + members: dict[str, bytes] + modes: dict[str, int] + metadata: Message + dist_info: str + + +class NoRedirects(urllib.request.HTTPRedirectHandler): + def redirect_request(self, *args: object, **kwargs: object) -> None: + raise ValueError("Pinned artifact downloads must not redirect") + + +def _download(url: str, digest: str, destination: Path) -> Path: + parsed = urlsplit(url) + assert parsed.scheme == "https" and parsed.netloc == "files.pythonhosted.org", "Unapproved artifact origin" + assert not parsed.query and not parsed.fragment, "Artifact URLs cannot contain queries or fragments" + assert re.fullmatch(r"[a-f0-9]{64}", digest), "A SHA256 pin is required" + assert parsed.path.endswith(".whl"), "Expected a wheel URL" + opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirects()) + size = 0 + checksum = hashlib.sha256() + started = time.monotonic() + with opener.open(url, timeout=30) as response, destination.open("xb") as output: + assert response.status == 200, "Artifact download did not succeed" + while chunk := response.read(1024 * 1024): + size += len(chunk) + assert size <= _MAX_DOWNLOAD, "Artifact exceeds the 150 MiB download limit" + assert time.monotonic() - started < 30, "Artifact download exceeded 30 seconds" + checksum.update(chunk) + output.write(chunk) + assert checksum.hexdigest() == digest, "Downloaded artifact SHA256 differs from its pin" + return destination + + +def _regular_member(entry: ZipInfo) -> None: + parts = PurePosixPath(entry.filename).parts + assert parts and not entry.filename.startswith("/"), "Absolute wheel member" + assert ".." not in parts and "\\" not in entry.filename, "Unsafe wheel member" + assert not entry.is_dir(), "Explicit directory members are not supported" + assert PurePosixPath(entry.filename).as_posix() == entry.filename, "Noncanonical wheel member" + assert (entry.external_attr >> 16) & 0o170000 in (0, 0o100000), "Nonregular wheel member" + + +def _member_inventory(entries: list[ZipInfo], expected_root: str) -> None: + names = [entry.filename for entry in entries] + assert len(names) == len(set(names)), "Duplicate wheel member" + assert len(entries) <= 10000, "Wheel member limit exceeded" + assert sum(entry.file_size for entry in entries) <= _MAX_EXPANDED, "Expanded wheel size limit exceeded" + for entry in entries: + _regular_member(entry) + roots = {name.split("/", 1)[0] for name in names if name.split("/", 1)[0].endswith(".dist-info")} + assert roots == {expected_root}, "Unexpected wheel distribution root" + + +def _record_entry(members: dict[str, bytes], record_path: str, row: list[str]) -> None: + member, digest, size = row + if member == record_path: + assert digest == size == "", "RECORD must leave its own hash and size empty" + return + expected = base64.urlsafe_b64encode(hashlib.sha256(members[member]).digest()).rstrip(b"=").decode("ascii") + assert digest == f"sha256={expected}", f"RECORD hash differs for {member}" + assert size == str(len(members[member])), f"RECORD size differs for {member}" + + +def _record_inventory(members: dict[str, bytes], record_path: str) -> None: + rows = list(csv.reader(io.StringIO(members[record_path].decode("utf-8")))) + assert all(len(row) == 3 for row in rows), "Malformed wheel RECORD" + assert len(rows) == len({row[0] for row in rows}), "Duplicate wheel RECORD member" + assert {row[0] for row in rows} == set(members), "RECORD does not cover exactly the wheel members" + for row in rows: + _record_entry(members, record_path, row) + + +def _wheel(path: Path, name: str, version: str) -> WheelContents: + expected_root = f"{name.replace('-', '_')}-{version}.dist-info" + with ZipFile(path) as archive: + entries = archive.infolist() + _member_inventory(entries, expected_root) + members = {entry.filename: archive.read(entry) for entry in entries} + modes = {entry.filename: entry.external_attr >> 16 for entry in entries} + metadata = BytesParser().parsebytes(members[f"{expected_root}/METADATA"]) + assert metadata.get_all("Name") == [name], "Wheel metadata name differs from selected distribution" + assert metadata.get_all("Version") == [version], "Wheel metadata version differs from selected distribution" + _record_inventory(members, f"{expected_root}/RECORD") + assert f"{expected_root}/WHEEL" in members, "Wheel metadata is missing" + return WheelContents(members, modes, metadata, expected_root) + + +def _certifi_selection(root: Path) -> tuple[str, str, str]: + lock = tomllib.loads((root / "uv.lock").read_text(encoding="utf-8")) + packages = [package for package in lock["package"] if package["name"] == "certifi"] + assert len(packages) == 1, "The frozen graph must select exactly one certifi distribution" + package = packages[0] + wheels = package["wheels"] + assert len(wheels) == 1, "Expected exactly one certifi wheel in the frozen graph" + version, url, digest = package["version"], wheels[0]["url"], wheels[0]["hash"] + assert digest.startswith("sha256:"), "Frozen certifi wheel must use SHA256" + _export_certifi_hash(root, version, digest) + return version, url, digest.removeprefix("sha256:") + + +def _export_certifi_hash(root: Path, version: str, digest: str) -> None: + export = (root / "requirements/ci/locked.txt").read_text(encoding="utf-8").replace("\\\n", " ") + lines = [line for line in export.splitlines() if line.startswith("certifi==")] + assert len(lines) == 1, "The exported graph must select exactly one certifi distribution" + assert re.match(rf"certifi=={re.escape(version)}(?:\s|$)", lines[0]), "Lock and export certifi versions differ" + assert f"--hash={digest}" in lines[0].split(), "Export omits the selected certifi wheel hash" + + +def test_frozen_certifi_bundle_excludes_compromised_root(tmp_path: Path, pytestconfig: pytest.Config) -> None: + version, url, digest = _certifi_selection(pytestconfig.rootpath) + path = _download(url, digest, tmp_path / "certifi.whl") + wheel = _wheel(path, "certifi", version) + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.load_verify_locations(cadata=wheel.members["certifi/cacert.pem"].decode("ascii")) + fingerprints = {hashlib.sha256(cert).hexdigest() for cert in context.get_ca_certs(binary_form=True)} + assert fingerprints, "Frozen certifi artifact must provide a nonempty CA trust store" + assert _BAD_CA not in fingerprints, "Frozen certifi artifact retains the compromised emSign root" + + +def test_artifact_download_rejects_wrong_sha256(tmp_path: Path, pytestconfig: pytest.Config) -> None: + _, url, _ = _certifi_selection(pytestconfig.rootpath) + with pytest.raises(AssertionError, match="Downloaded artifact SHA256 differs from its pin"): + _download(url, "0" * 64, tmp_path / "wrong-hash.whl") + + +def _malformed_members(fault: str) -> dict[str, bytes]: + members = { + "fixture-1.0.dist-info/METADATA": b"Metadata-Version: 2.1\nName: fixture\nVersion: 1.0\n", + "fixture-1.0.dist-info/WHEEL": b"Wheel-Version: 1.0\nTag: py3-none-any\n", + "fixture/data.txt": b"original data\n", + } + record_name = "fixture-1.0.dist-info/RECORD" + rows: list[list[str]] = [] + for name, payload in members.items(): + digest = base64.urlsafe_b64encode(hashlib.sha256(payload).digest()).rstrip(b"=").decode("ascii") + rows.append([name, f"sha256={digest}", str(len(payload))]) + rows.append([record_name, "", ""]) + if fault == "duplicate_record": + rows.append(rows[0]) + record = io.StringIO(newline="") + csv.writer(record).writerows(rows) + members[record_name] = record.getvalue().encode("utf-8") + mutations = { + "traversal": {"../outside.txt": b"untrusted"}, + "extra_member": {"fixture/unrecorded.txt": b"untrusted"}, + "corrupt_payload": {"fixture/data.txt": b"changed data\n"}, + "wrong_identity": {"fixture-1.0.dist-info/METADATA": b"Metadata-Version: 2.1\nName: other\nVersion: 1.0\n"}, + } + members.update(mutations.get(fault, {})) + return members + + +def _write_fixture(path: Path, members: dict[str, bytes]) -> None: + with ZipFile(path, "w") as archive: + for name, payload in members.items(): + archive.writestr(name, payload) + + +def _duplicate_fixture_member(path: Path) -> None: + with ZipFile(path, "a") as archive, pytest.warns(UserWarning, match="Duplicate name"): + archive.writestr("fixture/data.txt", b"duplicate") + + +@pytest.mark.parametrize( + ("fault", "message"), + [ + ("duplicate_member", "Duplicate wheel member"), + ("duplicate_record", "Duplicate wheel RECORD member"), + ("traversal", "Unsafe wheel member"), + ("extra_member", "RECORD does not cover"), + ("corrupt_payload", "RECORD hash differs"), + ("wrong_identity", "metadata name differs"), + ], + ids=["duplicate_member", "duplicate_record", "traversal", "extra_member", "corrupt_payload", "wrong_identity"], +) +def test_wheel_reader_rejects_malformed_artifacts(tmp_path: Path, fault: str, message: str) -> None: + path = tmp_path / "fixture.whl" + _write_fixture(path, _malformed_members(fault)) + if fault == "duplicate_member": + _duplicate_fixture_member(path) + with pytest.raises(AssertionError, match=message): + _wheel(path, "fixture", "1.0") + + +@pytest.mark.parametrize( + "url", + [ + "http://files.pythonhosted.org/data.whl", + "https://example.org/data.whl", + "https://user:password@files.pythonhosted.org/data.whl", + "https://files.pythonhosted.org/data.whl?token=credential", + ], + ids=["http", "other-host", "credentials", "query"], +) +def test_artifact_download_rejects_unapproved_urls(tmp_path: Path, url: str) -> None: + with pytest.raises(AssertionError, match=r"origin|queries"): + _download(url, "a" * 64, tmp_path / "artifact.whl") diff --git a/tests/unit/security/test_frozen_gitpython_compatibility.py b/tests/unit/security/test_frozen_gitpython_compatibility.py new file mode 100644 index 00000000..51cd3c72 --- /dev/null +++ b/tests/unit/security/test_frozen_gitpython_compatibility.py @@ -0,0 +1,81 @@ +import json +import re +import shutil +import subprocess +import sys +from importlib.metadata import distribution +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[3] +FROZEN_WHEEL_INSTALL_FLAGS = ( + "--no-config", + "pip", + "install", + "--only-binary=:all:", + "--no-deps", + "--require-hashes", + "--index-url", + "https://pypi.org/simple", +) +SUBMODULE_PROBE = """ +import json, sys +from pathlib import Path +from unittest.mock import patch +sys.path.extend(sys.argv[1:-1]) +import git +from git.objects.submodule.base import Submodule +workspace = Path(sys.argv[-1]) +parent = git.Repo.init(workspace / "parent") +submodule = Submodule(parent, Submodule.NULL_BIN_SHA, name="module", + path="../outside", url="unused") +rejected = False +with patch.object(Submodule, "_clone_repo", side_effect=AssertionError("clone boundary reached")) as clone: + try: + submodule.update(init=True) + except ValueError: + rejected = True + clone_calls = clone.call_count +print(json.dumps({"version": git.__version__, "rejected": rejected, + "clone_calls": clone_calls, "outside_exists": (workspace / "outside").exists()})) +""" + + +def _frozen_gitpython_requirement(tmp_path: Path) -> tuple[Path, str]: + locked = (REPO_ROOT / "requirements/ci/locked.txt").read_text(encoding="utf-8") + entries = re.findall(r"(?m)^gitpython==([^\s]+) \\\n((?:[ \t]+--hash=sha256:[0-9a-f]{64}(?: \\)?\n)+)", locked) + assert len(entries) == 1, "Expected one hash-pinned GitPython delivery requirement" + version, hashes = entries[0] + requirement = tmp_path / "gitpython.txt" + requirement.write_text(f"gitpython=={version} \\\n{hashes}", encoding="utf-8") + return requirement, version + + +def test_submodule_update_rejects_outside_checkout_before_clone(tmp_path: Path) -> None: + requirement, version = _frozen_gitpython_requirement(tmp_path) + uv = shutil.which("uv") + assert uv is not None, "The frozen delivery probe requires uv" + target = tmp_path / "delivery-package" + install_command = [uv, *FROZEN_WHEEL_INSTALL_FLAGS, "--target", str(target), "-r", str(requirement)] + subprocess.run( + install_command, + check=True, + capture_output=True, + text=True, + timeout=120, + ) + dependency_roots = {str(distribution(package).locate_file("")) for package in ("gitdb", "smmap")} + probe_arguments = [str(target), *sorted(dependency_roots), str(tmp_path)] + observed = subprocess.run( + [sys.executable, "-I", "-S", "-c", SUBMODULE_PROBE, *probe_arguments], + check=False, + capture_output=True, + text=True, + timeout=10, + ) + assert observed.returncode == 0, observed.stderr + result = json.loads(observed.stdout) + assert result["version"] == version + assert result["rejected"] is True + assert result["clone_calls"] == 0 + assert result["outside_exists"] is False diff --git a/tests/unit/security/test_frozen_jwt_compatibility.py b/tests/unit/security/test_frozen_jwt_compatibility.py new file mode 100644 index 00000000..46655ed4 --- /dev/null +++ b/tests/unit/security/test_frozen_jwt_compatibility.py @@ -0,0 +1,84 @@ +import json +import re +import shutil +import subprocess +import sys +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[3] +JWT_PROBE = """ +import json, sys +sys.path.insert(0, sys.argv[1]) +import jwt +key = "local-regression-key-with-at-least-thirty-two-bytes" +token = jwt.encode({"sub": "fixture", "exp": 1}, key, algorithm="HS256") +options = {"verify_signature": False} +subject = jwt.decode(token, options=options)["sub"] +after_inspection = dict(options) +options["verify_signature"] = True +expired = False +try: + jwt.decode(token, key, algorithms=["HS256"], options=options) +except jwt.ExpiredSignatureError: + expired = True +signed = jwt.encode({"sub": "fixture"}, key, algorithm="HS256") +signed_options = {} +signed_claims = jwt.decode(signed, key, algorithms=["HS256"], options=signed_options) +padded = signed + "=" * (-len(signed.rsplit(".", 1)[1]) % 4) +padded_claims = jwt.decode(padded, key, algorithms=["HS256"], options=signed_options) +print(json.dumps({"version": jwt.__version__, "subject": subject, + "after_inspection": after_inspection, "expired": expired, + "signed_claims": signed_claims, "padded_claims": padded_claims, + "signed_options": signed_options})) +""" + + +def test_inspection_preserves_options_and_expiry_on_reuse(tmp_path: Path) -> None: + """Exercise the hash-pinned delivery package independently of verifier dependencies.""" + locked = (REPO_ROOT / "requirements/ci/locked.txt").read_text(encoding="utf-8") + entries = re.findall(r"(?m)^pyjwt==([^\s]+) \\\n((?:[ \t]+--hash=sha256:[0-9a-f]{64}(?: \\)?\n)+)", locked) + assert len(entries) == 1, "Expected one hash-pinned PyJWT delivery requirement" + version, hashes = entries[0] + requirement = tmp_path / "pyjwt.txt" + requirement.write_text(f"pyjwt=={version} \\\n{hashes}", encoding="utf-8") + uv = shutil.which("uv") + assert uv is not None, "The frozen delivery probe requires uv" + target = tmp_path / "delivery-package" + subprocess.run( + [ + uv, + "--no-config", + "pip", + "install", + "--target", + str(target), + "--only-binary=:all:", + "--no-deps", + "--require-hashes", + "--index-url", + "https://pypi.org/simple", + "-r", + str(requirement), + ], + check=True, + capture_output=True, + text=True, + timeout=120, + ) + observed = subprocess.run( + [sys.executable, "-I", "-S", "-c", JWT_PROBE, str(target)], + check=True, + capture_output=True, + text=True, + timeout=10, + ) + result = json.loads(observed.stdout) + assert result["version"] == version + assert result["subject"] == "fixture" + assert result["after_inspection"] == {"verify_signature": False} + assert result["expired"] is True + + assert result["signed_claims"] == {"sub": "fixture"} + assert result["padded_claims"] == {"sub": "fixture"} + assert result["signed_options"] == {} diff --git a/tests/unit/security/test_release_dependency_selection.py b/tests/unit/security/test_release_dependency_selection.py new file mode 100644 index 00000000..0c06c564 --- /dev/null +++ b/tests/unit/security/test_release_dependency_selection.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import re +import tomllib +from pathlib import Path + +import pytest + + +REPO_ROOT = Path(__file__).resolve().parents[3] + + +@pytest.mark.parametrize( + ("name", "version"), + [("hatchling", "1.32.4"), ("urllib3", "2.8.0"), ("virtualenv", "21.11.0"), ("python-discovery", "1.6.0")], +) +def test_reviewed_tooling_versions_match_frozen_export(name: str, version: str) -> None: + lock = tomllib.loads((REPO_ROOT / "uv.lock").read_text(encoding="utf-8")) + packages = [package for package in lock["package"] if package["name"] == name] + assert len(packages) == 1 + assert packages[0]["version"] == version + assert packages[0]["source"] == {"registry": "https://pypi.org/simple"} + export = (REPO_ROOT / "requirements/ci/locked.txt").read_text(encoding="utf-8") + assert re.search(rf"^{re.escape(name)}=={re.escape(version)}\s", export, re.MULTILINE) + + +@pytest.mark.parametrize( + ("name", "version"), + [("jekyll-feed", "0.18.0"), ("jekyll-redirect-from", "0.17.0"), ("jekyll-relative-links", "0.9.1")], +) +def test_reviewed_documentation_plugins_are_locked(name: str, version: str) -> None: + locked = (REPO_ROOT / "docs/Gemfile.lock").read_text(encoding="utf-8") + assert re.search(rf"^ {re.escape(name)} \({re.escape(version)}\)$", locked, re.MULTILINE) + + +def test_documentation_json_has_reviewed_floor_and_major_bound() -> None: + gemfile = (REPO_ROOT / "docs/Gemfile").read_text(encoding="utf-8") + assert 'gem "json", ">= 2.21.2", "< 3"' in gemfile diff --git a/tests/unit/security/test_release_promotion_security_gates.py b/tests/unit/security/test_release_promotion_security_gates.py index 81daeb93..cf69989e 100644 --- a/tests/unit/security/test_release_promotion_security_gates.py +++ b/tests/unit/security/test_release_promotion_security_gates.py @@ -4,6 +4,7 @@ import importlib.util import json +import re import subprocess import sys import tomllib @@ -389,21 +390,41 @@ def test_every_code_review_lock_install_has_exact_closure_proof() -> None: assert install.index(closure_proof) < install.index("uv pip install") -def test_frozen_graph_uses_fixed_semgrep_mcp_pair_without_waiver() -> None: - """All authoritative dependency surfaces select the reviewed fixed line.""" +def test_frozen_graph_uses_official_semgrep_with_bounded_mcp_exception() -> None: project = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")) dev = cast(list[str], project["project"]["optional-dependencies"]["dev"]) scanning = cast(list[str], project["project"]["optional-dependencies"]["scanning"]) - assert "semgrep>=1.175.0" in dev - assert "semgrep>=1.175.0" in scanning + assert {"semgrep>=1.175.0"} <= set(dev) & set(scanning) locked = (REPO_ROOT / "requirements" / "ci" / "locked.txt").read_text(encoding="utf-8") - assert "semgrep==1.175.0" in locked - assert "mcp==1.29.0" in locked + assert set(re.findall(r"^(?:semgrep|mcp)==[^\s]+", locked, re.MULTILINE)) == {"semgrep==1.179.0", "mcp==1.29.0"} policy = json.loads((REPO_ROOT / "ci" / "security-tool-minimum-versions.json").read_text(encoding="utf-8")) assert policy["minimum_versions"] == {"mcp": "1.28.1", "semgrep": "1.175.0"} exceptions = json.loads((REPO_ROOT / "ci" / "vulnerability-audit-exceptions.json").read_text(encoding="utf-8")) - assert all(record.get("package") != "mcp" for record in exceptions["exceptions"]) + records = exceptions["exceptions"] + assert len(records) == 1, "Only the reviewed MCP advisory may be waived" + record = records[0] + expected_record = { + "package": "mcp", + "version": "1.29.0", + "vulnerability_ids": ["CVE-2026-59951", "GHSA-84m7-p3x7-pcfv"], + "reviewed_on": "2026-10-21", + "expires_on": "2026-11-04", + } + actual_record = {key: record[key] for key in expected_record} + actual_record["vulnerability_ids"] = sorted(record["vulnerability_ids"]) + assert actual_record == expected_record + assert tuple(bool(record[key].strip()) for key in ("rationale", "mitigation")) == (True, True) + lock = tomllib.loads((REPO_ROOT / "uv.lock").read_text(encoding="utf-8")) + packages = [ + (package["name"], package["version"], package["source"]) + for package in lock["package"] + if package["name"] in {"semgrep", "mcp", "specfact-semgrep"} + ] + assert sorted(packages) == [ + ("mcp", "1.29.0", {"registry": "https://pypi.org/simple"}), + ("semgrep", "1.179.0", {"registry": "https://pypi.org/simple"}), + ] def test_code_review_lock_is_bound_before_installation(tmp_path: Path) -> None: @@ -563,9 +584,15 @@ def run_rg(arguments: list[str], **_kwargs: object) -> subprocess.CompletedProce def test_patch_release_uses_next_version_in_all_sources() -> None: - """The security baseline consumes only the next semver patch.""" - expected = "0.55.4" + expected = "0.55.11" project = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")) assert project["project"]["version"] == expected - for path in ("setup.py", "src/__init__.py", "src/specfact_cli/__init__.py"): - assert expected in (REPO_ROOT / path).read_text(encoding="utf-8") + patterns = { + "setup.py": r"version\s*=\s*[\"']([^\"']+)[\"']", + "src/__init__.py": r"(?m)^__version__\s*=\s*[\"']([^\"']+)[\"']", + "src/specfact_cli/__init__.py": r"(?m)^__version__\s*=\s*[\"']([^\"']+)[\"']", + } + for path, pattern in patterns.items(): + match = re.search(pattern, (REPO_ROOT / path).read_text(encoding="utf-8")) + assert match is not None, f"Missing version assignment in {path}" + assert match.group(1) == expected, path From e9cee5649ece27ff8fa8bc1058a5a27eadf1b4b8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:53:52 +0000 Subject: [PATCH 2/7] test(release): bind native proof to change failures Stage: prefix7 Source: 08126f3f8a8e46a79c46c574dfd7b0c44e915831 Ordinal: 2 Manifest: 6c3d344ecb69beca59231c3c49ae05dba50790fd253db87f37122028de1dfa2a Executor: bd639eb85d78f5a3d3438893297b31f5b3a93e88 Run: 39949294200 Attempt: 1 --- .../TDD_EVIDENCE.md | 44 +++++++ .../design.md | 12 ++ .../requirements-evidence.yaml | 124 +----------------- .../corrected-mapping-review.md | 83 ++++++++++++ .../requirements-proof/review-evidence.json | 8 +- 5 files changed, 145 insertions(+), 126 deletions(-) create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/corrected-mapping-review.md diff --git a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md index 24d6395c..e380f92b 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md +++ b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md @@ -1,5 +1,13 @@ # Test-first evidence +## Current proof status + +The corrected native plan selects nineteen cases. Its fresh local run reports +19 failed, 0 passed, 0 errors and 0 skipped. Independent AI review accepted the +corrected mapping. Fresh native RED acceptance remains pending. The initial 34-case planning, +review and local run below are historical. The first native artifact is incomplete. +All fifteen passing controls remain unchanged in the required test suite. + ## Local RED before production edits Captured on 2026-10-22 Europe/Berlin against main commit 5262efb0 in the @@ -86,3 +94,39 @@ to reduce its line count. Independent mapping review verified this boundary. The exact local report and changed-line comparison are retained under `/private/tmp/specfact-release-20261022/red-owner/test-only-code-review.json` and `code-review-line-triage.json`. Native CI RED and final release gates remain pending. + +## First native attempt and corrected proof selection + +GitHub run [39809800598](https://github.com/nold-ai/specfact-cli/actions/runs/39809800598), +at test-only commit `dd1b4121944f247c4d5dff0903902e2721d8c1a6`, retained +artifact `12440155998`. It executed all 34 exact cases with genuine failures +and passing controls. Its unchanged report has `gate_decision=fail`, +`observed_maturity=incomplete` and fifteen `red-proof-passed-not-failed` findings. +This artifact is not accepted RED. Its original bytes remain unchanged. + +The pinned native lifecycle requires each RED selector to fail. Earlier planning +and review incorrectly assumed passing safety controls could share that plan. +The corrected selection retains all nineteen actually failing cases. Fifteen +already-passing malformed-artifact and negative-audit controls remain unchanged +and enforced by the normal required test suite. They will run during final +verification. This changes no assertion or production behavior and waives no +check. A fresh independent review, test-only commit and accepted native RED +are required before dependency edits. The earlier 34-case review is historical +evidence, not acceptance of this corrected mapping. + +## Corrected mapping acceptance and proposal bytes + +Independent AI reviewer `/root/release_corrected_mapping_review` using +gpt-6-astra accepted the nineteen-case mapping. Current acceptance references +`requirements-proof/corrected-mapping-review.md` and source mapping digest +`sha256:1abe2b6dc3e8a294795a00236803b002b98ad02ec05b22b2b0deb2cbf13a2020`. +All nineteen selectors have genuine recorded CI failures. All fifteen omitted +controls remain byte-identical in required tests. Local rerun of the corrected +selectors reports nineteen failures, no errors and no skips. + +The earlier report's proposal hash predates normal Markdown autolink formatting. +The new independent review examined the exact two URL-wrapper changes and +accepts current proposal SHA256 +`83e860e44ebc7778c48fb0ef382ed26c4d8ebe80ccdcd18037f85c57f37d21df`. +The original report remains historical with its original byte identities. +Fresh native RED is still required before production edits. diff --git a/openspec/changes/isolate-semgrep-security-runtime/design.md b/openspec/changes/isolate-semgrep-security-runtime/design.md index 34950c05..21571f8c 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/design.md +++ b/openspec/changes/isolate-semgrep-security-runtime/design.md @@ -36,3 +36,15 @@ Native mapped proof covers the Python artifact behavior, release selections, source build, bounded audit decision, and version metadata. Real Ruby behavior published-runtime equality, and changelog scope are separate final acceptance checks. Their output must be retained before final review and is not implied by the Python mapping. + +## Native failing-first selection + +The pinned native reconciler requires every RED-plan selector to fail. The first +34-case CI attempt retained genuine output but refused fifteen passing controls. +It remains incomplete evidence and must not be relabelled RED. The corrected +plan contains nineteen genuinely failing change cases. The fifteen malformed +artifact and negative audit controls remain unchanged in their test files and +in the normal required pytest suite. They are supplementary safety checks, +not new behavior needing failure-before-fix provenance. Final verification runs +both the nineteen selected cases and all supplementary controls. No check, +assertion, production file, or native policy is changed by this correction. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml b/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml index b593dbdd..01daa1ad 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml @@ -35,94 +35,6 @@ requirements: selector: runner: pytest node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_frozen_certifi_bundle_excludes_compromised_root - - case_id: CERTIFI-WRONG-HASH - scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust - method: test - intent: Reject downloaded bytes that differ from the pinned hash. - observable: The downloader raises a SHA256 mismatch. - selector: - runner: pytest - node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_wrong_sha256 - - case_id: WHEEL-DUPLICATE-MEMBER - scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust - method: test - intent: Reject a malformed wheel before using its contents. - observable: The duplicate_member fixture is refused with its specific diagnostic. - selector: - runner: pytest - node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[duplicate_member] - - case_id: WHEEL-DUPLICATE-RECORD - scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust - method: test - intent: Reject a malformed wheel before using its contents. - observable: The duplicate_record fixture is refused with its specific diagnostic. - selector: - runner: pytest - node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[duplicate_record] - - case_id: WHEEL-TRAVERSAL - scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust - method: test - intent: Reject a malformed wheel before using its contents. - observable: The traversal fixture is refused with its specific diagnostic. - selector: - runner: pytest - node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[traversal] - - case_id: WHEEL-EXTRA-MEMBER - scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust - method: test - intent: Reject a malformed wheel before using its contents. - observable: The extra_member fixture is refused with its specific diagnostic. - selector: - runner: pytest - node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[extra_member] - - case_id: WHEEL-CORRUPT-PAYLOAD - scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust - method: test - intent: Reject a malformed wheel before using its contents. - observable: The corrupt_payload fixture is refused with its specific diagnostic. - selector: - runner: pytest - node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[corrupt_payload] - - case_id: WHEEL-WRONG-IDENTITY - scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust - method: test - intent: Reject a malformed wheel before using its contents. - observable: The wrong_identity fixture is refused with its specific diagnostic. - selector: - runner: pytest - node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[wrong_identity] - - case_id: WHEEL-ORIGIN-8 - scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust - method: test - intent: Reject an unapproved artifact URL before download. - observable: The origin or query validation rejects the URL. - selector: - runner: pytest - node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[http] - - case_id: WHEEL-ORIGIN-9 - scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust - method: test - intent: Reject an unapproved artifact URL before download. - observable: The origin or query validation rejects the URL. - selector: - runner: pytest - node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[other-host] - - case_id: WHEEL-ORIGIN-10 - scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust - method: test - intent: Reject an unapproved artifact URL before download. - observable: The origin or query validation rejects the URL. - selector: - runner: pytest - node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[credentials] - - case_id: WHEEL-ORIGIN-11 - scenario_id: exclude-compromised-certificate-authorities-from-delivered-trust - method: test - intent: Reject an unapproved artifact URL before download. - observable: The origin or query validation rejects the URL. - selector: - runner: pytest - node_id: tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[query] openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:reject-submodule-checkouts-outside-the-repository: rationale: Inspect the delivery artifact independently of installed verifier packages. stakeholder_refs: *id001 @@ -145,8 +57,8 @@ requirements: scenario_id: preserve-jwt-verification-options-and-compatibility method: test intent: Exercise the frozen JWT artifact in an isolated process. - observable: The frozen artifact preserves inspection options, rejects expiry on reuse, and decodes signed - and padded tokens without mutating caller options. + observable: The frozen artifact preserves inspection options, rejects expiry on reuse, and decodes signed and padded + tokens without mutating caller options. selector: runner: pytest node_id: tests/unit/security/test_frozen_jwt_compatibility.py::test_inspection_preserves_options_and_expiry_on_reuse @@ -243,38 +155,6 @@ requirements: selector: runner: pytest node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[expiry-day] - - case_id: MCP-OTHER-VERSION - scenario_id: bound-the-temporary-official-mcp-advisory-exception - method: test - intent: Exercise the committed register through the existing strict audit gate. - observable: Only the approved package, version, and advisory inside the inclusive date window is waived. - selector: - runner: pytest - node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[other-version] - - case_id: MCP-UNRELATED-ADVISORY - scenario_id: bound-the-temporary-official-mcp-advisory-exception - method: test - intent: Exercise the committed register through the existing strict audit gate. - observable: Only the approved package, version, and advisory inside the inclusive date window is waived. - selector: - runner: pytest - node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[unrelated-advisory] - - case_id: MCP-MIXED-ADVISORIES - scenario_id: bound-the-temporary-official-mcp-advisory-exception - method: test - intent: Exercise the committed register through the existing strict audit gate. - observable: Only the approved package, version, and advisory inside the inclusive date window is waived. - selector: - runner: pytest - node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[mixed-advisories] - - case_id: MCP-SKIPPED - scenario_id: bound-the-temporary-official-mcp-advisory-exception - method: test - intent: Exercise the committed register through the existing strict audit gate. - observable: Only the approved package, version, and advisory inside the inclusive date window is waived. - selector: - runner: pytest - node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[skipped] - case_id: MCP-EXPIRED scenario_id: bound-the-temporary-official-mcp-advisory-exception method: test diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/corrected-mapping-review.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/corrected-mapping-review.md new file mode 100644 index 00000000..5cdac254 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/corrected-mapping-review.md @@ -0,0 +1,83 @@ +# Corrected dependency release mapping review + +TL;DR. Accepted for the corrected 19-case technical mapping. This is independent AI review, not human approval, accepted native RED, permission to merge, or release readiness. Production edits still require a fresh authentic native RED run for the corrected test-only commit. Confidence is high for the mapping correction because the staged diff, exact test bytes, native reconciliation rule, and original CI JUnit agree. + +Reviewed on 2026-10-22, Europe/Berlin, by Codex agent `/root/release_corrected_mapping_review`. The checkout is `/private/tmp/specfact-release-20261022/worktrees/hotfix/security-dependencies-0-55-11`, branch `hotfix/security-dependencies-0-55-11`, HEAD `dd1b4121944f247c4d5dff0903902e2721d8c1a6`. The review is limited to the staged correction and its supplied evidence. No test suite, build, hook, signing action, commit, or production edit was performed. Only this report was written. Throughput checkpoint is not applicable to this read-only review. The poteto-mode investigation and unslop guidance informed the evidence-first report. + +The reviewed staged files are `TDD_EVIDENCE.md`, `design.md`, `requirements-evidence.yaml`, and `requirements-proof/review-evidence.json` under `openspec/changes/isolate-semgrep-security-runtime`. The working bytes equal their staged blobs. The review record correctly remains pending with a placeholder digest during independent review. The earlier `requirements-proof/independent-review.md` is historical and does not provide acceptance for this correction. + +## Verdict basis + +The correction removes precisely the 15 already-passing controls from the native failing-first mapping. It preserves the other 19 case records semantically, every requirement, all scenario IDs, all rationale/stakeholder records, and every touchpoint. No specification, test assertion, fixture, helper, application code, audit policy, native lifecycle, hook, or workflow changes occur in this staged correction. `git diff dd1b412 -- tests scripts .github ci pyproject.toml uv.lock requirements/ci/locked.txt` is empty. + +The pinned lifecycle at `/Users/dom/.codex/worktrees/core-review-reset-fixture/specfact-cli-modules/packages/specfact-requirements/src/specfact_requirements/requirements/lifecycle.py:511` requires each RED selector to have exactly one `failed` outcome. The initial assumption that a RED plan could also contain passing controls was incorrect. The correction follows that rule without changing it. + +I independently matched every retained exact selector against the original artifact's JUnit. All 19 have one `` and no `` or ``. All 15 omitted selectors have passing outcomes. The original 34-case set equals the corrected 19 plus the 15 supplied supplementary records. There are no newly omitted failing cases. + +| Requirement scenario ID | Retained cases | Original CI failure basis | +| --- | ---: | --- | +| exclude-compromised-certificate-authorities-from-delivered-trust | 1 | Frozen certifi CA data contains the named fingerprint. | +| reject-submodule-checkouts-outside-the-repository | 1 | Frozen GitPython reaches the patched clone boundary for `../outside`. | +| preserve-jwt-verification-options-and-compatibility | 1 | Frozen PyJWT mutates caller options during inspection. | +| select-the-reviewed-compatible-tooling-graph | 4 | Hatchling, urllib3, virtualenv, and python-discovery select old versions. | +| bound-the-temporary-official-mcp-advisory-exception | 6 | Old Semgrep selection, missing exact exception acceptance, and missing date-specific register diagnostics. | +| rebuild-the-source-distribution-with-its-bundled-manifest | 1 | Ordinary offline sdist-to-wheel build cannot find the forced bundle-mapper include. | +| select-the-reviewed-documentation-dependency-versions | 4 | Three plugin lock selections and the JSON declaration do not match the reviewed targets. | +| publish-one-coherent-patch-version | 1 | Version metadata remains 0.55.4. | + +The eight IDs above match the actual eight `#### Scenario` headings after native-style slug normalization. The seven test files remain byte-identical to HEAD. + +The 11 artifact controls cover one wrong SHA256, six malformed wheels, and four disallowed URL forms. These validate test support that already refuses hostile input. They do not require a production fix. The four other controls cover a different MCP version, unrelated advisory, mixed advisories, and skipped dependencies. The existing audit gate already fails these inputs. All 15 remain necessary after the exception is added. Their original passing results cannot establish that the future exception stays narrow, so their final rerun is mandatory. + +For MCP-EXPIRED and MCP-FUTURE-REVIEW, the existing gate already returns failure. Their RED assertions fail because the empty committed register produces the generic vulnerability diagnostic instead of `not currently valid`. These cases therefore demonstrate incomplete register/date-specific behavior. They are not evidence that the old gate accepted expired or future exceptions. Keeping them in the plan is technically valid with that limitation stated. The exact record test and the exact-CVE, exact-GHSA, and expiry-day cases provide the substantive positive change coverage. All negative cases must pass after the exact register entry is added. + +The normal Python 3.12 full-suite step remains in `.github/workflows/pr-orchestrator.yml`, invoking `python tools/smart_test_coverage.py run --level full`; the Python 3.11 compatibility step invokes `python -m pytest -r fEw tests/unit tests/integration`. The affected test files remain in those directories with no new skip or deselection. This establishes retained configuration, not a claim that final CI has already passed. + +The 12 intended production touchpoints remain mapped. They are `pyproject.toml`, `uv.lock`, `requirements/ci/locked.txt`, `ci/vulnerability-audit-exceptions.json`, `setup.py`, `src/__init__.py`, `src/specfact_cli/__init__.py`, `CHANGELOG.md`, `docs/Gemfile`, `docs/Gemfile.lock`, `scripts/check_jekyll_dependencies.rb`, and `.github/workflows/docs-dependency-checks.yml`. Native cases cover dependency/artifact behavior, declarations, version metadata, and source-build inclusion. Real Ruby behavior, published runtime equality, signed-manifest preservation, and changelog scope remain separate final checks as the design states. Mapping touchpoints does not itself prove those checks. + +## Immutable evidence and identities + +Original GitHub run [39809800598](https://github.com/nold-ai/specfact-cli/actions/runs/39809800598) is recorded at HEAD `dd1b4121944f247c4d5dff0903902e2721d8c1a6`, artifact ID `12440155998`. Local evidence is under `/private/tmp/specfact-release-20261022/native-red/run-39809800598-artifact-12440155998`. I checked the downloaded archive SHA256 against its saved GitHub artifact metadata and each extracted file byte-for-byte against its archive member. I did not independently refetch GitHub metadata in this read-only review. + +- Archive SHA256 is `b7c7d2d8b41daf8441b1b7ec2461ce3916ccb64b5be0e51d5e9daf6e66310c43`. +- Original report SHA256 is `87122f8964ab3d8188e4ea9f9c8d74f3b5f3d65ccd69be3c23d7cd8a654aaa8c`. +- Original JUnit SHA256 is `5d0c4f72df0252e79ce1d49a948e9efe8e3a345e779390ec538cb74768768d24`. +- The unchanged report says `gate_decision=fail`, `observed_maturity=incomplete`, and `required_maturity=red`, with exactly 15 `red-proof-passed-not-failed` findings. + +I independently recomputed the corrected digests using the pinned lifecycle's typed canonical JSON encoding and normalized plan construction. The supplied corrected planning report is only `planned`, despite its passing planning verdict. + +| Identity | SHA256 | +| --- | --- | +| Staged mapping file bytes | `55da4dc879b2fca6a78b383ad10920630d279e1d1ff1beb1a79d335d26323e42` | +| Native source mapping digest for review binding | `1abe2b6dc3e8a294795a00236803b002b98ad02ec05b22b2b0deb2cbf13a2020` | +| Native combined mapping digest | `e787fd8484005fa90644edfb5060dbebc15632195957652dd1ef03b4c9adcc9f` | +| Native plan digest | `02f8055fb0ddf38115533549fdb0dd093f001f7a99786e450e05e49626b0acf2` | +| Native normalized plan identity digest | `7d0b5500870357f8c6d802e3e33e1d51dc274441157e679a3ea55417f7ec8acb` | + +The source mapping digest, with the `sha256:` prefix, is the acceptance digest for this independent technical mapping review. The combined mapping digest is the normalized execution identity and must not be substituted for the source digest in the source review record. + +| Unchanged test file | SHA256 | +| --- | --- | +| tests/integration/test_distribution_build.py | `af24d9caa1259ecf9789440c516784ea01d4afb34acf8fe9ef377f5f2f46806b` | +| tests/unit/scripts/test_security_audit_gate.py | `d0b1fcb16185b1908b6abc53e0c68e7baf04ede2b395492fa4d4cbb37ed8c3b9` | +| tests/unit/security/test_frozen_dependency_artifacts.py | `146f45786ccb74b581c36a1a626130c540631e45fa869433d5663e56202b4215` | +| tests/unit/security/test_frozen_gitpython_compatibility.py | `527d78a6dd07e04e00fefa06632cfa320725f035c22d988e0a1741a455ece728` | +| tests/unit/security/test_frozen_jwt_compatibility.py | `35b8e0d571bf50a30599ad1634b068d2d3d37e9a46ffd45fe6dfea03771cab16` | +| tests/unit/security/test_release_dependency_selection.py | `7e41674b9b6c2bfb210f50084125821e54c662b079d932be2ba2ebb787bf876f` | +| tests/unit/security/test_release_promotion_security_gates.py | `dcf6de107fea93357421daae229bbdd144d0e592d56b28c58b0429aa4ddd9531` | + +The typed canonical digest of the mapping from those seven paths to their SHA256 values is `sha256:9126c9f4d25cb03d55890fa5dd2bc5a365593c6c13aded5b3cc6deb2f337a015`. This inventory is not a substitute for the required full test/import/conftest freeze. + +## Limits and next actions + +No blocking finding remains in this 19-case planning correction. Acceptance assumes the reviewed scope remains dependency-only 0.55.11 with published 0.55.4 runtime preservation except version, the exact MCP 1.29.0 advisory exception through 2026-11-04 inclusive, ordinary CI/hooks, and CI-only signing. Any changes to selected tests, helpers, ancestor conftest files, transitive imports, native policy, or exception scope require renewed validation and may invalidate this verdict. + +The principal failure modes are treating the failed original artifact as RED, allowing a broad exception to pass because the 15 controls are no longer selected by the native plan, or reading Python metadata checks as proof of Ruby/runtime compatibility. Mitigations are a new accepted native RED artifact bound to the corrected commit, a required final full-suite run including every supplementary control, and retained real Ruby plus published-runtime and signature evidence. The design and task list already require these checks. + +The coordinator should bind this real independent review to the source digest, commit only the reviewed test/planning correction with normal hooks, obtain authentic accepted native RED for that new commit, and freeze the test closure before production changes. Final verification must use the same native mapping/plan and rerun all 15 controls. Keep the original failed artifact and earlier review historical. Complete the documented internal wiki follow-up through the coordinating agent. No merge or publishing is authorized by this report. Rollback remains the unmerged PR diff. This review incurred only local read/hash work; CI runtime and final release costs were not measured. + +## Proposal change after the earlier review + +The coordinator reported PR 785 review comment `4350610610` concerning the prior proposal hash. I independently compared `proposal.md` from the earlier reviewed tree `9e61234b9130a48d8c7fac8e603881ec199176a7` to current committed HEAD `dd1b4121944f247c4d5dff0903902e2721d8c1a6`, read the current proposal in full, and verified that working bytes equal committed bytes. The only changes are Markdown angle-bracket autolink wrappers around the existing issue 780 and parent issue 747 URLs. Replacing precisely those two strings in the old bytes reproduces the current file exactly. There is no scope, acceptance-criterion, release-version, exception-date, dependency-target, runtime-preservation, or authorization change. + +The original proposal SHA256 was `87d33cfa09d1ad7ee89a7c4b45ab6b6d828b2b077f9f825a089c4ede622ac35c`. The current committed proposal SHA256 is `83e860e44ebc7778c48fb0ef382ed26c4d8ebe80ccdcd18037f85c57f37d21df`. This independent review explicitly accepts the current committed proposal bytes with the corrected 19-case mapping. The earlier report must keep its original hash and remain historical. The coordinator must point current acceptance to this report and record the post-review formatting change honestly. I verified the actual byte delta; the attribution to the ordinary pre-commit formatter comes from the coordinator and was not independently replayed. No hook was run or bypassed during this review. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json index f7d218bc..b740dbd1 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json @@ -1,9 +1,9 @@ { "schema_version": "1", "decision": "accepted", - "reviewer_id": "Codex:/root/release_mapping_review (gpt-6.1-sol)", + "reviewer_id": "Codex:/root/release_corrected_mapping_review (gpt-6-astra)", "reviewer_role": "independent AI technical mapping reviewer", - "recorded_at": "2026-10-22T15:55:08.939596+00:00", - "reference": "https://github.com/nold-ai/specfact-cli/blob/hotfix/security-dependencies-0-55-11/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/independent-review.md", - "mapping_digest": "sha256:8e3941ca05ccf9bceaea351df6cc1e642508debfc4ab87ecdc7b6b9cc45e6820" + "recorded_at": "2026-10-22T16:09:12.996170+00:00", + "reference": "https://github.com/nold-ai/specfact-cli/blob/hotfix/security-dependencies-0-55-11/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/corrected-mapping-review.md", + "mapping_digest": "sha256:1abe2b6dc3e8a294795a00236803b002b98ad02ec05b22b2b0deb2cbf13a2020" } From 4b3a9db3aa63a4c693e30c3232f70ef932e3e83e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:53:56 +0000 Subject: [PATCH 3/7] test(release): isolate the frozen GitPython closure Stage: prefix7 Source: a2ee2c9d96e1cce5ba1f13f733ccc49580262ffb Ordinal: 3 Manifest: 6c3d344ecb69beca59231c3c49ae05dba50790fd253db87f37122028de1dfa2a Executor: bd639eb85d78f5a3d3438893297b31f5b3a93e88 Run: 39949294200 Attempt: 1 --- .../TDD_EVIDENCE.md | 28 +++++++++++++++++++ .../proposal.md | 3 ++ .../frozen-closure-review.md | 27 ++++++++++++++++++ .../requirements-proof/review-evidence.json | 6 ++-- .../isolate-semgrep-security-runtime/tasks.md | 1 + .../test_frozen_gitpython_compatibility.py | 23 +++++++++------ 6 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/frozen-closure-review.md diff --git a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md index e380f92b..50ba92ae 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md +++ b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md @@ -130,3 +130,31 @@ accepts current proposal SHA256 `83e860e44ebc7778c48fb0ef382ed26c4d8ebe80ccdcd18037f85c57f37d21df`. The original report remains historical with its original byte identities. Fresh native RED is still required before production edits. + +## Frozen GitPython closure and explicit Ruby scope + +The nineteen-case mapping achieved accepted native RED at commit +`08126f3f8a8e46a79c46c574dfd7b0c44e915831`, run +[39811618606](https://github.com/nold-ai/specfact-cli/actions/runs/39811618606), +artifact `12439638334`. Its unchanged report has `gate_decision=pass`, +`observed_maturity=red`, and no findings. + +Subsequent review identified that the GitPython probe borrowed gitdb and smmap +from the verifier environment. The probe now installs the exact three hash-pinned +requirements into the isolated target. Its selector and assertions are unchanged. +Twelve private missing, duplicate and unhashed-declaration controls pass. A real +rerun installs the three wheels and fails at `AssertionError: clone boundary reached`. +An earlier DNS-limited invocation is not behavioral evidence. This test edit +requires fresh native RED at its new commit before production changes. + +The proposal and task now explicitly name creation of the already-mapped Ruby +compatibility probe and workflow. No new production touchpoint or release scope +is added. New independent review must accept the actual current proposal bytes. + +Independent AI reviewer `/root/release_probe_final_review` using gpt-6.1-sol +accepted the strengthened frozen closure, unchanged nineteen-case mapping and +current proposal SHA256 +`7a448d1b6702eb3abca880172f6286ac4e77948119023f07ea21796a4b4a8f97`. +Current technical acceptance references `requirements-proof/frozen-closure-review.md`. +Earlier review reports retain their original historical boundaries. Fresh native +RED at this test-only revision remains required before production changes. diff --git a/openspec/changes/isolate-semgrep-security-runtime/proposal.md b/openspec/changes/isolate-semgrep-security-runtime/proposal.md index ebfed1b0..674f7173 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/proposal.md +++ b/openspec/changes/isolate-semgrep-security-runtime/proposal.md @@ -18,6 +18,9 @@ proof for this main branch. - Refresh documentation gems to jekyll-feed 0.18.0, jekyll-redirect-from 0.17.0, and jekyll-relative-links 0.9.1. Require JSON >=2.21.2,<3 and verify the installed gem behavior separately. +- Add the scoped Ruby compatibility probe at `scripts/check_jekyll_dependencies.rb` + and its CI workflow at `.github/workflows/docs-dependency-checks.yml`, ported + from the reviewed dev implementation after native RED acceptance. - Include the existing bundle-mapper source in the source distribution so the standard source-to-wheel build preserves its existing manifest bytes. - Synchronize the four version sources to 0.55.11 and record the release in the changelog. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/frozen-closure-review.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/frozen-closure-review.md new file mode 100644 index 00000000..51d089fb --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/frozen-closure-review.md @@ -0,0 +1,27 @@ +# Frozen GitPython closure and Ruby scope review + +TL;DR. Accepted for the reviewed test correction, current proposal bytes, and unchanged technical mapping. No blocking finding. This is independent AI review by Codex agent `/root/release_probe_final_review`, not human approval or release readiness. Fresh native RED for the new test-only commit remains required before production edits. + +Reviewed on 2026-10-22, Europe/Berlin. Worktree `/private/tmp/specfact-release-20261022/worktrees/hotfix/security-dependencies-0-55-11`, branch `hotfix/security-dependencies-0-55-11`, HEAD `08126f3f8a8e46a79c46c574dfd7b0c44e915831`. Read-only investigation except this report. No code edit, staging, hook, commit, suite, build, signing, merge, or publication was performed. I read poteto-mode and its comment-sicko instructions and reviewed the actual staged diff and complete GitPython test. The staged TDD record and pending review record are consistent with the remaining gate. + +The helper now extracts exactly one canonical frozen declaration each for gitdb, gitpython, and smmap. It rejects missing, duplicate, duplicate-unhashed, and unhashed declarations before emitting requirements. All three retain their frozen SHA256 hashes and enter the same isolated target through the existing ordinary PyPI installer with `--no-config`, `--only-binary=:all:`, `--no-deps`, and `--require-hashes`. The subprocess retains `-I -S` and receives only that target as its added import path. Verifier distribution roots are removed. The regex matches the actual canonical frozen export. The exact selector and behavioral assertions are unchanged. No production algorithm or public API changes occur. + +I inspected `probe-fix/check_requirement_helper.py` and its supplied log. Its twelve hostile declaration checks cover all four rejection modes for each package. I checked every entry in `probe-fix/checkpoint-sha256.json` against actual bytes. The supplied `gitpython-red.xml` has one failure, zero errors, and zero skips. Its traceback enters GitPython from `delivery-package` and reaches the patched `_clone_repo` boundary with `AssertionError: clone boundary reached`. Installation therefore completed before the behavioral failure. The earlier DNS failure is not used as behavioral evidence. I did not rerun these checks. + +The proposal and task explicitly name `scripts/check_jekyll_dependencies.rb` and `.github/workflows/docs-dependency-checks.yml`. Both were already intended in the unchanged design and mapping. Their absence before production is consistent with test-first sequencing. This clarification preserves dependency-only 0.55.11, the published 0.55.4 runtime constraint except version strings, the exact MCP 1.29.0 exception through 2026-11-04 inclusive, existing CI/hooks, and CI-only signing. + +I independently recomputed the native typed source mapping digest as `sha256:1abe2b6dc3e8a294795a00236803b002b98ad02ec05b22b2b0deb2cbf13a2020`. It still contains nineteen cases across eight scenarios and twelve intended production touchpoints. Its selectors match the supplied native plan. No other mapped test or specification changed. The fifteen supplementary controls remain in ordinary pytest directories covered by the unchanged full-suite and compatibility workflow steps. Their final passing run remains required. + +The supplied native run 39811618606, artifact 12439638334, reports `gate_decision=pass`, `observed_maturity=red`, and no findings. Its JUnit contains nineteen failures, zero errors, and zero skips. Its execution proof binds HEAD `08126f3f8a8e46a79c46c574dfd7b0c44e915831` and the previous GitPython test hash `527d78a6dd07e04e00fefa06632cfa320725f035c22d988e0a1741a455ece728`. It does not cover the new test bytes below. The initial 34-case run remains incomplete historical evidence. I inspected supplied local artifacts without independently refetching GitHub metadata. + +The following identities bind this acceptance. Each working file equals its staged blob. + +| File | SHA256 | +| --- | --- | +| `tests/unit/security/test_frozen_gitpython_compatibility.py` | `0d0adc56f725dd4878024c6058c341574fd80222709ff4e47ebda259b9dd5f68` | +| `openspec/changes/isolate-semgrep-security-runtime/proposal.md` | `7a448d1b6702eb3abca880172f6286ac4e77948119023f07ea21796a4b4a8f97` | +| `openspec/changes/isolate-semgrep-security-runtime/tasks.md` | `63ee07869603773d08a6375c1730d9d6d476663c22e8adfb7e545a6ea2a86b29` | + +Comment review covered the scoped GitPython test and staged additions. New code comments, suppressions, deletion recommendations, actual deletions, restored comments, reruns, and `MUST KILL` flags are all zero. No workaround or open constraint was found. Production implementations are outside this review. + +Confidence is high for this static correction and mapping acceptance. It assumes the exact reviewed bytes and authorized release boundaries remain intact. The material failure modes are reusing old RED after changing the test closure, losing supplementary negative controls, or treating Python selection checks as Ruby compatibility proof. Obtain fresh native RED and freeze its complete test/import/conftest closure, rerun all controls during final verification, and retain real Ruby plus runtime/signature evidence. Bind current acceptance to this report and source digest after stopping the reviewer. Commit with normal hooks and obtain fresh native RED at the resulting test-only head before production edits. No merge or publication is authorized. Review cost was local read/hash work; CI duration was not measured. Rollback remains the unmerged PR diff. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json index b740dbd1..7a17d155 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json @@ -1,9 +1,9 @@ { "schema_version": "1", "decision": "accepted", - "reviewer_id": "Codex:/root/release_corrected_mapping_review (gpt-6-astra)", + "reviewer_id": "Codex:/root/release_probe_final_review (gpt-6.1-sol)", "reviewer_role": "independent AI technical mapping reviewer", - "recorded_at": "2026-10-22T16:09:12.996170+00:00", - "reference": "https://github.com/nold-ai/specfact-cli/blob/hotfix/security-dependencies-0-55-11/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/corrected-mapping-review.md", + "recorded_at": "2026-10-22T16:18:44.881679+00:00", + "reference": "https://github.com/nold-ai/specfact-cli/blob/hotfix/security-dependencies-0-55-11/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/frozen-closure-review.md", "mapping_digest": "sha256:1abe2b6dc3e8a294795a00236803b002b98ad02ec05b22b2b0deb2cbf13a2020" } diff --git a/openspec/changes/isolate-semgrep-security-runtime/tasks.md b/openspec/changes/isolate-semgrep-security-runtime/tasks.md index 7e084877..00bca80f 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/tasks.md +++ b/openspec/changes/isolate-semgrep-security-runtime/tasks.md @@ -6,6 +6,7 @@ - [ ] Refresh the eight reviewed official Python packages and both frozen files. - [ ] Add only the exact approved MCP advisory exception. - [ ] Update documentation dependency declarations and lock, then execute real gem compatibility probes. +- [ ] After native RED acceptance, port the reviewed dev Ruby compatibility probe to `scripts/check_jekyll_dependencies.rb` and its CI workflow to `.github/workflows/docs-dependency-checks.yml`. - [ ] Add the missing bundle-mapper source-distribution inclusion and synchronize version 0.55.11 with the changelog. - [ ] Verify published runtime equality, strict module signatures, both audit lanes, and required local and CI gates. - [ ] Complete independent final review and present the unmerged PR. diff --git a/tests/unit/security/test_frozen_gitpython_compatibility.py b/tests/unit/security/test_frozen_gitpython_compatibility.py index 51cd3c72..640ec911 100644 --- a/tests/unit/security/test_frozen_gitpython_compatibility.py +++ b/tests/unit/security/test_frozen_gitpython_compatibility.py @@ -3,7 +3,6 @@ import shutil import subprocess import sys -from importlib.metadata import distribution from pathlib import Path @@ -43,12 +42,21 @@ def _frozen_gitpython_requirement(tmp_path: Path) -> tuple[Path, str]: locked = (REPO_ROOT / "requirements/ci/locked.txt").read_text(encoding="utf-8") - entries = re.findall(r"(?m)^gitpython==([^\s]+) \\\n((?:[ \t]+--hash=sha256:[0-9a-f]{64}(?: \\)?\n)+)", locked) - assert len(entries) == 1, "Expected one hash-pinned GitPython delivery requirement" - version, hashes = entries[0] + requirements: dict[str, tuple[str, str]] = {} + for package in ("gitdb", "gitpython", "smmap"): + declarations = re.findall(rf"(?m)^{package}(?=[\s=<>!~;\[@]|$)", locked) + assert len(declarations) == 1, f"Expected one {package} delivery requirement" + entries = re.findall( + rf"(?m)^{package}==([^\s]+) \\\n((?:[ \t]+--hash=sha256:[0-9a-f]{{64}}(?: \\)?\n)+)", locked + ) + assert len(entries) == 1, f"Expected one hash-pinned {package} delivery requirement" + requirements[package] = entries[0] requirement = tmp_path / "gitpython.txt" - requirement.write_text(f"gitpython=={version} \\\n{hashes}", encoding="utf-8") - return requirement, version + requirement.write_text( + "".join(f"{package}=={version} \\\n{hashes}" for package, (version, hashes) in requirements.items()), + encoding="utf-8", + ) + return requirement, requirements["gitpython"][0] def test_submodule_update_rejects_outside_checkout_before_clone(tmp_path: Path) -> None: @@ -64,8 +72,7 @@ def test_submodule_update_rejects_outside_checkout_before_clone(tmp_path: Path) text=True, timeout=120, ) - dependency_roots = {str(distribution(package).locate_file("")) for package in ("gitdb", "smmap")} - probe_arguments = [str(target), *sorted(dependency_roots), str(tmp_path)] + probe_arguments = [str(target), str(tmp_path)] observed = subprocess.run( [sys.executable, "-I", "-S", "-c", SUBMODULE_PROBE, *probe_arguments], check=False, From 2f851708e545e88ce91874a79422fa31a918f686 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:54:00 +0000 Subject: [PATCH 4/7] test(release): refresh official dependency proof Stage: prefix7 Source: 8cce44f449e9559d0100064d629c81991784ab41 Ordinal: 4 Manifest: 6c3d344ecb69beca59231c3c49ae05dba50790fd253db87f37122028de1dfa2a Executor: bd639eb85d78f5a3d3438893297b31f5b3a93e88 Run: 39949294200 Attempt: 1 --- .../CHANGE_VALIDATION.md | 6 + .../TDD_EVIDENCE.md | 119 +++++++++++++- .../design.md | 26 +++- .../proposal.md | 16 +- .../requirements-evidence.yaml | 36 ++++- .../correction-mapping-review.md | 147 ++++++++++++++++++ .../correction-review-pending.md | 17 ++ .../pre-correction-review-evidence.json | 9 ++ .../requirements-proof/review-evidence.json | 8 +- .../specs/isolated-scanner-runtime/spec.md | 2 +- .../isolate-semgrep-security-runtime/tasks.md | 8 +- .../test_release_dependency_selection.py | 11 +- tests/unit/versioning/test_analyzer.py | 2 +- 13 files changed, 383 insertions(+), 24 deletions(-) create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-mapping-review.md create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-review-pending.md create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-correction-review-evidence.json diff --git a/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md b/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md index 4aec407d..13140762 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md +++ b/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md @@ -6,3 +6,9 @@ The coordinating agent verified Todo status, labels, project, and hierarchy. The dev change was read as context. Its admission artifacts and dev-only CI changes are excluded. Genuine main native RED is required before production edits. No independently accepted mapping or passing evidence is claimed yet. + +The owner explicitly approved the virtualenv correction on 2026-10-22. The +coordinator rechecked issue #780 as OPEN/Todo with labels, project, and hierarchy +intact, and updated the internal wiki mirror and rebuilt its graph. The corrected +release selects nine changed Python packages relative to main. Fresh independent +acceptance and native RED remain pending for the changed specification and tests. diff --git a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md index 50ba92ae..d95a3268 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md +++ b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md @@ -2,11 +2,18 @@ ## Current proof status -The corrected native plan selects nineteen cases. Its fresh local run reports -19 failed, 0 passed, 0 errors and 0 skipped. Independent AI review accepted the -corrected mapping. Fresh native RED acceptance remains pending. The initial 34-case planning, -review and local run below are historical. The first native artifact is incomplete. -All fifteen passing controls remain unchanged in the required test suite. +The owner-approved correction now selects 23 cases. It changes virtualenv to +21.14.12 and python-discovery to 1.6.1, adds packaging 26.3, and binds the exact +certifi 2026.10.19, GitPython 3.2.0, and PyJWT 2.15.1 selections. The artifact +probes and all fifteen supplementary controls remain intact. A fresh local run has 23 failures, zero errors, and zero skips. All fifteen +supplementary controls pass. Independent acceptance and fresh native RED are +pending for this changed proof. + +Every review and native result below applies only to its original mapping and +test bytes. None supplies acceptance or native RED for the corrected 23-case +mapping. The prior acceptance receipt is retained unchanged in +`requirements-proof/pre-correction-review-evidence.json`. The active receipt +has decision `pending` and a repository-relative status reference. ## Local RED before production edits @@ -158,3 +165,105 @@ current proposal SHA256 Current technical acceptance references `requirements-proof/frozen-closure-review.md`. Earlier review reports retain their original historical boundaries. Fresh native RED at this test-only revision remains required before production changes. + +## Approved correction after the candidate audit + +On 2026-10-22 Europe/Berlin the strict primary frozen audit rejected the prior +virtualenv 21.11.0 candidate for these five findings: + +- CVE-2026-113726 / GHSA-5vjq-rrrf-7h2q +- CVE-2026-113776 / GHSA-8rjx-v5ww-45pp +- CVE-2026-113732 / GHSA-c947-3pg5-gm8q +- CVE-2026-113725 / GHSA-vxj4-q4g9-crj7 +- CVE-2026-113722 / GHSA-c978-59m8-c77v + +The actual failed audit is retained in +`/private/tmp/specfact-release-20261022/implementation/security-audits.log`. +The owner explicitly approved correction to official virtualenv 21.14.12 and +its required python-discovery 1.6.1 and packaging 26.3. The authenticated wheel +metadata and ordinary scratch resolution are retained under +`/private/tmp/specfact-release-20261022/audit/virtualenv-correction/`. +That resolution changed exactly those three versions relative to the prior +candidate, with no package added or removed. No new exception is authorized. + +The previous 19-case proof was accepted at test-only commit +`a2ee2c9d96e1cce5ba1f13f733ccc49580262ffb`, run +[39813086468](https://github.com/nold-ai/specfact-cli/actions/runs/39813086468), +artifact `12441520223`. Its report has `gate_decision=pass` and +`observed_maturity=red`. It is superseded for this correction because the spec, +test table, and mapping bytes changed. Its original artifacts remain untouched. +The prior candidate's 19 selected passes and 64 regression-file passes are also +historical evidence, not verification of the newly approved graph. + +Before editing the spec or tests, all twelve production paths matched the +implementation checkpoint and its saved copies. A second full patch and file +backup is retained under +`/private/tmp/specfact-release-20261022/correction-tests/`. Ten tracked production +files were restored to HEAD, and only the two backed-up new Ruby files were +removed. No production correction has been applied. The dedicated verifier +environment was preserved. Artifact probes obtain the restored baseline's exact +frozen wheels independently of that environment's installed package versions. + +The existing Git consumer fixture correction remains test-only. Its recursive +staging had included `.git/config`, causing three genuine GitPython 3.2.0 setup +failures. The one-line `repo.git.add("--all")` correction preserves the consumer +assertions. The original failures and 29 passing affected tests are retained in +`implementation/git-consumer-regression.log` and +`implementation/git-consumer-regression-fixed.log` beneath the private release +directory. Independent review accepted that fixture correction in +`candidate-precorrection-review.md`. This path is outside the native mapped proof. + +Review findings `PRRT_kwDOQLaPE86vRvLU` and `PRRT_kwDOQLaPE86vR4YF` are valid. +The existing parameter table now asserts exact lock and export selections for +certifi, GitPython, PyJWT, and packaging. The active acceptance reference is +repository-relative and awaits the coordinator's fresh independent reviewer +receipt. The old mutable branch URL is retained only in the historical receipt. + +The canonical planned command uses the immutable release base because the local +`main` ref selects unrelated changes. The initial broader planning failure is +retained as `planning.json`; `planning-base.json` and `plan-base.json` are the +successful canonical outputs from the unchanged released CLI: + +```sh +. /private/tmp/specfact-release-20261022/red-owner/env.sh +export GITHUB_BASE_REF=main +python scripts/requirements_evidence_delivery_gate.py \ + --base-ref 5262efb0881e4fac7697d629b0530250342bc656 \ + --required-maturity planned \ + --output /private/tmp/specfact-release-20261022/correction-tests/planning-base.json \ + --summary /private/tmp/specfact-release-20261022/correction-tests/planning-base.md \ + --plan-output /private/tmp/specfact-release-20261022/correction-tests/plan-base.json +``` + +Initial sandbox runs failed DNS resolution while fetching official wheels. Their +`sandbox-*.log` and `sandbox-*.xml` outputs are retained and are not behavioral +RED or passing control evidence. The unchanged commands were then rerun with +network access. Exact pytest argument arrays are retained in +`correction-tests/mapped-command.json` and `correction-tests/controls-command.json`. +Fresh independent acceptance, normal hooks, and genuine native RED at the new +revision remain required before production reapplication. + +## Corrected local failing-first result + +The network-enabled run completed with **23 failed, 0 passed, 0 errors, and +0 skipped**. The separate controls run completed with **15 passed, 0 failed, +0 errors, and 0 skipped**. These are local observations, not native CI acceptance. + +The actual certifi wheel retains the compromised root. The GitPython probe +reaches `AssertionError: clone boundary reached`. PyJWT adds seven verification +options to the caller's dictionary. The ordinary offline source-to-wheel build +fails because the source archive lacks `modules/bundle-mapper`. The exact-version +cases fail on the restored older lock values, including packaging 26.2 rather +than 26.3. Other cases retain the expected old release/documentation versions +and absent MCP exception diagnostics. No network or install failure is counted +as behavioral proof in this result. + +The canonical combined mapping digest is `sha256:6d114e1c20cf149c24c310009d9a3f975ee1cf4626c6d67842d0033d029c147f`. +The plan identity digest is `sha256:29af99fc48f7afd6a0dc641716e9ccb5c8bf230ef290848cf5331738a33e7880`. +`correction-tests/mapped-local-red.xml` SHA256 is +`49653320cc55fdc741b5344850631c6e9e067a4fed40598b5296175326884bd4`. +`correction-tests/controls-green.xml` SHA256 is +`c4715403faf3bcae521030eea59784ee09d645119d80a55b92a41d1e8b5a75be`. +The sibling raw logs, argument arrays, and `failure-diagnostics.json` preserve +every case's actual failure message. The artifact paths are relative to +`/private/tmp/specfact-release-20261022/`. diff --git a/openspec/changes/isolate-semgrep-security-runtime/design.md b/openspec/changes/isolate-semgrep-security-runtime/design.md index 21571f8c..b0f2173d 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/design.md +++ b/openspec/changes/isolate-semgrep-security-runtime/design.md @@ -41,10 +41,26 @@ must be retained before final review and is not implied by the Python mapping. The pinned native reconciler requires every RED-plan selector to fail. The first 34-case CI attempt retained genuine output but refused fifteen passing controls. -It remains incomplete evidence and must not be relabelled RED. The corrected -plan contains nineteen genuinely failing change cases. The fifteen malformed +It remains incomplete evidence and must not be relabelled RED. That historical corrected +plan contained nineteen genuinely failing change cases. The fifteen malformed artifact and negative audit controls remain unchanged in their test files and in the normal required pytest suite. They are supplementary safety checks, -not new behavior needing failure-before-fix provenance. Final verification runs -both the nineteen selected cases and all supplementary controls. No check, -assertion, production file, or native policy is changed by this correction. +not new behavior needing failure-before-fix provenance. The newly approved dependency correction expands the plan to 23 cases. +It adds exact certifi, GitPython, PyJWT, and packaging lock/export selections +and updates the virtualenv and python-discovery expectations. Final verification +runs all 23 selected cases and all fifteen supplementary controls. +No artifact behavior assertion, negative control, or native policy is weakened. + +## Corrected dependency selection and review boundary + +The existing parameter table binds the eight exact runtime and tooling versions +to both uv.lock and its hashed export. The separate scanner case binds official +Semgrep 1.179.0 and MCP 1.29.0. Artifact probes continue to inspect the actual +frozen certifi, GitPython, and PyJWT wheels. Exact-version assertions complement +those behavior checks. + +The prior accepted 19-case proof applies only to its original test and mapping +bytes. Keep its reports as history. A new acceptance record remains pending +until independent review of the corrected mapping, and its reference must be +repository-relative or pinned to an immutable commit. The coordinator must +verify fresh native RED before restoring the saved production patch. diff --git a/openspec/changes/isolate-semgrep-security-runtime/proposal.md b/openspec/changes/isolate-semgrep-security-runtime/proposal.md index 674f7173..0a9ca1e2 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/proposal.md +++ b/openspec/changes/isolate-semgrep-security-runtime/proposal.md @@ -11,8 +11,8 @@ proof for this main branch. ## What Changes - Refresh official Python packages to certifi 2026.10.19, GitPython 3.2.0, - Hatchling 1.32.4, PyJWT 2.15.1, urllib3 2.8.0, virtualenv 21.11.0, - python-discovery 1.6.0, and Semgrep 1.179.0. Preserve MCP 1.29.0. + Hatchling 1.32.4, PyJWT 2.15.1, urllib3 2.8.0, virtualenv 21.14.12, + python-discovery 1.6.1, packaging 26.3, and Semgrep 1.179.0. Preserve MCP 1.29.0. - Bound the existing advisory register to MCP 1.29.0 and its reviewed advisory, with review date 2026-10-21 and expiry 2026-11-04 inclusive. - Refresh documentation gems to jekyll-feed 0.18.0, @@ -50,6 +50,8 @@ outside this release. No local signing, merge, or publication is authorized. ## Acceptance Criteria +- The lock and hashed export select every exact reviewed Python version. + - The exact frozen certificate wheel excludes the compromised emSign root. - The exact GitPython wheel rejects an outside submodule path before cloning. - The exact PyJWT wheel preserves caller options and expiry verification on reuse. @@ -57,3 +59,13 @@ outside this release. No local signing, merge, or publication is authorized. - A real offline source-to-wheel build includes the unchanged bundled manifest. - Native CI retains genuine test-first failures before dependency or release edits. - Required quality checks, independent review, strict signatures, and dependency audits pass before the PR is offered for merge. + +## Approved correction on 2026-10-22 + +The owner approved replacing virtualenv 21.11.0 after the strict audit reported +five additional advisories. The official virtualenv 21.14.12 wheel requires +python-discovery >=1.6.1 and packaging >=26.3. The corrected selections are +21.14.12, 1.6.1, and 26.3. All other reviewed targets remain unchanged. +The retained scratch resolution changes only these three packages relative to +the prior candidate, with no added or removed packages. Fresh independent +mapping acceptance and native RED are required before applying production edits. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml b/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml index 01daa1ad..a67ac634 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml @@ -67,6 +67,38 @@ requirements: stakeholder_refs: *id001 touchpoints: *id002 verification_cases: + - case_id: CERTIFI-FROZEN-SELECTION + scenario_id: select-the-reviewed-compatible-tooling-graph + method: test + intent: Check reviewed certifi 2026.10.19 selection and export parity. + observable: The official registry lock version and exported requirement both match the reviewed version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[certifi-2026.10.19] + - case_id: GITPYTHON-FROZEN-SELECTION + scenario_id: select-the-reviewed-compatible-tooling-graph + method: test + intent: Check reviewed gitpython 3.2.0 selection and export parity. + observable: The official registry lock version and exported requirement both match the reviewed version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[gitpython-3.2.0] + - case_id: PYJWT-FROZEN-SELECTION + scenario_id: select-the-reviewed-compatible-tooling-graph + method: test + intent: Check reviewed pyjwt 2.15.1 selection and export parity. + observable: The official registry lock version and exported requirement both match the reviewed version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[pyjwt-2.15.1] + - case_id: PACKAGING-FROZEN-SELECTION + scenario_id: select-the-reviewed-compatible-tooling-graph + method: test + intent: Check reviewed packaging 26.3 selection and export parity. + observable: The official registry lock version and exported requirement both match the reviewed version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[packaging-26.3] - case_id: HATCHLING-FROZEN-SELECTION scenario_id: select-the-reviewed-compatible-tooling-graph method: test @@ -90,7 +122,7 @@ requirements: observable: The official registry lock version and exported requirement both match the reviewed version. selector: runner: pytest - node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[virtualenv-21.11.0] + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[virtualenv-21.14.12] - case_id: PYTHON-DISCOVERY-FROZEN-SELECTION scenario_id: select-the-reviewed-compatible-tooling-graph method: test @@ -98,7 +130,7 @@ requirements: observable: The official registry lock version and exported requirement both match the reviewed version. selector: runner: pytest - node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[python-discovery-1.6.0] + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[python-discovery-1.6.1] openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:bound-the-temporary-official-mcp-advisory-exception: rationale: Keep both main audit lanes strict with one exact, expiring owner-approved exception. stakeholder_refs: diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-mapping-review.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-mapping-review.md new file mode 100644 index 00000000..155318e3 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-mapping-review.md @@ -0,0 +1,147 @@ +# Corrected release mapping independent technical review + +TL;DR. **Accepted** for this corrected test-only mapping and the exact reviewed source bytes below. No blocking technical finding. This is fresh independent AI technical review by Codex agent `/root/release_correction_mapping_review`. It is not human approval, native RED acceptance, GREEN verification, signing authority, merge authority, or publication authority. + +Reviewed on 2026-10-22 Europe/Berlin against worktree `/private/tmp/specfact-release-20261022/worktrees/hotfix/security-dependencies-0-55-11`, branch `hotfix/security-dependencies-0-55-11`, HEAD `a2ee2c9d96e1cce5ba1f13f733ccc49580262ffb`. I did not write this correction. I read the actual correction diff, current proposal/specification/mapping/tests, restored production files, local failure messages and JUnit, controls, pending receipt, historical report context, correction metadata, and fixture regression evidence. No repository edit, staging, commit, test suite, installation, build, signing, PR action, merge, or publication was performed. My only executed verification beyond read/hash/Git inspection was a fresh planned-maturity run of the unchanged released Requirements CLI, writing separate reviewer artifacts outside the repository. + +The exact base is `5262efb0881e4fac7697d629b0530250342bc656`. My fresh `correction-review-plan.json` equals supplied `correction-tests/plan-final.json` as parsed data. Both pass at planned maturity. Source mapping digest is `sha256:9405ba32fc071ee2092077f516c4d7baf57ac0acde1575025d5545c0cbda04a8`. Combined mapping digest is `sha256:6d114e1c20cf149c24c310009d9a3f975ee1cf4626c6d67842d0033d029c147f`. Plan identity digest is `sha256:29af99fc48f7afd6a0dc641716e9ccb5c8bf230ef290848cf5331738a33e7880`. The embedded plan digest is `sha256:550fc6841c666d357054b55803472f65d3a67ed2fbb626df31f1e36e9853e604`. + +The actual post-format proposal SHA256 is `4ee2f880701c1c91532aaa8d904e82dcaad206a2be6eb41d9badd3b6d4e9a9c4`. All checkpoint entries matched actual files at review. All ten tracked production files are byte-identical to HEAD. The two backed-up new Ruby production files are absent. The complete base diff contains only OpenSpec/proof records and tests. No runtime, audit implementation, workflow, hook, manifest, signature, transitive Python helper, or ancestor conftest change was hidden in the correction. Six mapped test files other than the selection table are byte-identical to HEAD. The selection table's only code change is its approved package/version data. The separate analyzer fixture has exactly the one-line `repo.git.add("--all")` replacement. + +## Coverage and actual failing-first evidence + +The mapping contains 23 distinct selectors across all eight requirement/scenario pairs. Its selector set equals `selectors.json` and the actual 23 JUnit case names. Each case failed with an assertion on the specified restored baseline behavior or selection. `mapped-local-red.xml` records 23 failures, zero passes, zero errors, and zero skips. Its SHA256 is `49653320cc55fdc741b5344850631c6e9e067a4fed40598b5296175326884bd4`. This is host-enabled local evidence, not a fresh native CI artifact. I inspected actual failure message attributes and diagnostic tails, rather than treating a nonzero process status as proof. + +| Requirement/scenario | Selected cases and decisive observed failure | +| --- | --- | +| Exclude compromised certificate authorities | One real wheel/CA parse retains the exact emSign DER fingerprint. Hash/origin and RECORD/metadata validation run before that assertion. | +| Reject outside submodule checkout | One frozen GitPython/gitdb/smmap closure installs with hashes and no dependencies, enters the isolated target, and reaches `AssertionError: clone boundary reached`. Installation completed before this behavioral failure. | +| Preserve JWT options and compatibility | One frozen PyJWT probe returns seven unexpected caller-option mutations. The test retains expiry reuse, signed decode, padding, and signed-options assertions for GREEN. | +| Select reviewed compatible tooling graph | Eight exact official registry versions and lock/export parity checks. Restored versions differ for certifi, GitPython, PyJWT, Hatchling, urllib3, virtualenv, python-discovery, and packaging. | +| Bound official MCP advisory exception | Six cases bind official Semgrep/MCP and the exact register; exercise CVE and alias, inclusive expiry day, expired date, and future review date. Failures are old Semgrep selection, absent waiver, and missing date-invalid diagnostic. | +| Rebuild source distribution with manifest | One ordinary offline source-to-wheel build reaches the backend and fails because the sdist lacks `modules/bundle-mapper`. This is not an unavailable build tool or download failure. | +| Select documentation dependency versions | Three lock plugin versions and the JSON >=2.21.2,<3 declaration fail against old selections/floor. | +| Publish coherent patch version | One data-only test retains all four canonical version assertions. The restored first source reports 0.55.4 instead of 0.55.11. | + +Initial sandbox DNS/download failures are explicitly rejected as behavioral proof. Their historical `sandbox-*` artifacts must remain historical. Local evidence cannot supply native provenance. Several first assertions intentionally prevent later assertions running on the old baseline. Final GREEN must exercise those later checks. + +The fifteen supplementary controls remain unchanged. Actual `controls-green.xml` records fifteen passes, zero failures/errors/skips, SHA256 `c4715403faf3bcae521030eea59784ee09d645119d80a55b92a41d1e8b5a75be`. Eleven cover malformed/hash/origin artifact rejection. Four cover wrong MCP version, unrelated/mixed advisories, and skipped dependencies. Excluding already-passing controls from a reconciler requiring every RED selector to fail is reasonable because their assertions remain required during final verification. Run all seven complete mapped test files for GREEN, not merely the 23 selected cases. Those files are the artifact, GitPython, JWT, release-selection, promotion-security, audit-gate, and distribution-build test files named in the manifest below. The analyzer consumer fixture remains an additional affected regression surface. + +The fixture correction stages ordinary Git worktree contents through Git and preserves every consumer assertion. Supplied original evidence has three failures and 26 passes. Corrected evidence has 29 passes. I read those logs without rerunning them. This test-only correction is outside the native mapped proof and does not mask the frozen artifact probe's failure. + +## Scope and evidence boundary + +The correction selects official virtualenv 21.14.12, python-discovery 1.6.1, and packaging 26.3. Retained wheel metadata requires the latter two minimums. Supplied scratch-resolution data changes exactly those three versions relative to the prior candidate, adds/removes no package, and records `adopted=false`. Current proposal/spec/table retain all other exact targets, including certifi 2026.10.19, GitPython 3.2.0, and PyJWT 2.15.1. This yields nine Python dependency version updates relative to main, with core 0.55.11 and MCP 1.29.0 preserved as specified. Exact selection assertions complement, and do not replace, the frozen artifact behavior tests. + +The active receipt honestly remains pending with a repository-relative status reference. The saved pre-correction receipt equals the old HEAD receipt byte-for-byte. Historical reports keep their original scopes and hashes. Their old nineteen-case acceptance/native runs cannot accept these changed tests. The coordinator may now bind a fresh accepted receipt to this report using a repository-relative durable path and the source mapping digest. Do not infer human approval from the reviewer's identity or an accepted machine receipt. + +The known stale candidate exception rationale needs its separate correction after fresh native RED. This report does not approve additional exceptions, alternate exact dependency targets, a fork/override, check bypass, local signing, merge, or publication. Real Ruby behavior, strict audits, signatures, published-runtime equality, final quality checks, native GREEN, and final independent production review remain outside this mapping acceptance and still required. + +## Comment and semantic flags + +Read-only comment review covered the correction hunks and nearby code. New code comments, scoped suppressions, deletion recommendations, actual deletions, restored comments, reruns, `MUST KILL` flags, workaround flags, and open constraint encodings are all **zero**. No application edit or architecture sketch was needed. Existing external dependency and public-contract context was not treated as a new correction defect. Prior candidate Ruby comment cleanup recommendations remain separate historical review work because those files are currently absent. + +Confidence is High for static scope, exact identities, requirement coverage, local behavioral failures, and planned CLI digest agreement. This acceptance assumes these bytes and the full test/import/conftest closure remain frozen through native execution. Three material failure modes remain. Changing proof bytes invalidates old provenance, so re-review and obtain fresh native evidence. Passing the selected cases while missing control or later assertions can conceal regressions, so require full seven-file GREEN and affected consumers. Treating selection tests as audit/Ruby/runtime proof overstates readiness, so retain those separate actual results. Review cost was local read/hash/planning computation; no paid or deployment action occurred and no performance claim is made. Rollback is the preserved unmerged correction/production patches and file backups. + +Next action is a durable accepted technical receipt, normal hooks and test-only commit, then fresh genuine native CI RED before production reapplication. Keep historical proofs unchanged. + +## Exact reviewed input identities + +`worktree/` paths below are repository-relative under the reviewed worktree. Other release paths are relative to `/private/tmp/specfact-release-20261022`. `pstack-0.9.96/` paths are relative to the installed skill directory. SHA256 values bind the actual reviewed input bytes, including proposal after formatting. Reviewer-generated planned artifacts are included explicitly. The report itself is a new output and is not an input. + +```json +{ + "audit/virtualenv-correction/METADATA.txt": "a4902e1954e11c6c06a5e235de89ca273dd8fb6b7ad93d35f076194a8d09e1ee", + "audit/virtualenv-correction/resolution-result.json": "62fed2895c346541d79b73faf1e63f01c095bd3e5780226007c717eaef688c0e", + "candidate-precorrection-review.md": "53c85521be72a8ac11ccddfa0674a3992cdad6f6714a2e796bad32950f910824", + "correction-review-plan.json": "a5a96590762270c706387d1ba99b1c9dc990c09ae6496ff9f96cce0de74fd7c9", + "correction-review-planning.json": "3681852d17eae60e327b150a41b5c05eeb076b60428d10e900eb44ff3b822423", + "correction-tests/checkpoint-sha256.json": "0b9145751b7f90267a3201d1a6e20ca3471e2bd5374d2a4ef46a38805104613c", + "correction-tests/checkpoint.md": "d8050a0a9bcdfd6ac89dfe98ef8d994c0b2bdf062099aff48bb40a287e4cfc27", + "correction-tests/control-selectors.json": "b9d5ab7f19da658719c64105c65ee02a5d7914c7480d27e404330d5752d7e757", + "correction-tests/controls-command.json": "cdc0d2d0bbb91880041dfe638637dc138b01c5ec3e66e4992f849334a227f2b9", + "correction-tests/controls-exit.json": "e89df0eae5ec7db81135aed16463968955c578bcc4f2ec43172eff351f080c17", + "correction-tests/controls-green.xml": "c4715403faf3bcae521030eea59784ee09d645119d80a55b92a41d1e8b5a75be", + "correction-tests/correction.patch": "ea92dfacae85af2cf8091ffd7e986fb9f1d1270530ee22e31ce45fb8c97a20d2", + "correction-tests/failure-diagnostics.json": "81a28e2c73d00fc9a4bb742f631089e851b57f7bcfc8504fafc0ed2552d2ecaf", + "correction-tests/mapped-command.json": "75cc4169e5bc8b19fa47134eb4ba6c20a92eb2b82cc425cc57277e5778593810", + "correction-tests/mapped-exit.json": "5cc319f54b05a38799195a491c303aa686eab7caccfdbbf1e8b8c07405cb7392", + "correction-tests/mapped-local-red.xml": "49653320cc55fdc741b5344850631c6e9e067a4fed40598b5296175326884bd4", + "correction-tests/plan-base.json": "a5a96590762270c706387d1ba99b1c9dc990c09ae6496ff9f96cce0de74fd7c9", + "correction-tests/plan-final.json": "a5a96590762270c706387d1ba99b1c9dc990c09ae6496ff9f96cce0de74fd7c9", + "correction-tests/planning-final.json": "3681852d17eae60e327b150a41b5c05eeb076b60428d10e900eb44ff3b822423", + "correction-tests/pre-correction-production.patch": "d0ca6e1c88cf62cc79e14132cc539ff7a3e1277709981ea4c8e641a1220b2fd6", + "correction-tests/run-evidence.py": "db283d821e8633f8fa96219aa32c2021c3f671c418b886c4387a81b8b5414650", + "correction-tests/selectors.json": "54e819d757e2b63aa877bc91f88d13ff6530ea37c0f3cb48bba1c28bb792f164", + "implementation/git-consumer-regression-fixed.log": "226295020010569b7601e49a465e42c7c20b1b30a32c7646ae447ee679629fcc", + "implementation/git-consumer-regression.log": "a6b9f9e58a0fe63f81759915c60cff4705b1ff968c1fbc0ff19ae050222290b8", + "pstack-0.9.96/no-comments/SKILL.md": "550d10e87e4110a29bba5f966c784569dd1f070a0b715d03309c2eb652cc719e", + "pstack-0.9.96/poteto-mode/SKILL.md": "c0dc3827dd20ff0acadc1ade8f2139a62f62f1e7692e5de1e4c88f5bc89e5f08", + "pstack-0.9.96/poteto-mode/references/agents/comment-sicko.md": "3e239e40b7b4b9beb2cf7f3ccc57f2388eb8b69bd1ba8ae888ef87ef6e0e2116", + "pstack-0.9.96/poteto-mode/references/codex-tools.md": "c2f1d9bebdb157ea2b4e6e8d2f66a3b70a3ca3744e9fef0ff10436e24971b92c", + "red-owner/env.sh": "8a2b1fa4222a07b130aa269dd8443c14921c959484ebd67760337e3dfc9d79b7", + "worktree/AGENTS.md": "95b70856d492435262e9a86dbc42388682c44b9841b0a56c580838b986c9b5f8", + "worktree/CHANGELOG.md": "9ae1d0a51c81d99c0dfdf18bb1faae269d8a49ff6525866fc646d3f8d655db91", + "worktree/ci/vulnerability-audit-exceptions.json": "584e697cce0eb005ea6f0037f5757f418c6cad912bcca8f486ed97c7cdfa7fe1", + "worktree/docs/Gemfile": "66d9ce7302ab7d4335ea4e0345b72b0f330f29fbc210fdb25f46cf17792dcbe6", + "worktree/docs/Gemfile.lock": "1f92f6ac98a73680f8945a96ec5dd6a1bafd3b7489ccecaade42a0cac4ba0ea3", + "worktree/docs/agent-rules/05-non-negotiable-checklist.md": "44495f2ddb73b01174b8fb0eb9ffc894de4866c520372815b9c1732a2164a7ac", + "worktree/docs/agent-rules/10-session-bootstrap.md": "a4cf51a60499ff1acc76143a02042ca7d64a84b5236790cc8dd74258d52f7fb0", + "worktree/docs/agent-rules/INDEX.md": "6fd104400707fd8264ba3ae743b991fdc009cb2d4fa68232235142a242317a6c", + "worktree/openspec/changes/isolate-semgrep-security-runtime/.openspec.yaml": "0ad492a63d87e00a8bc135b0a1a2b19270dbb9ffd997a69b89fd4ab72015a944", + "worktree/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md": "0db5c7bf37bc263840f7140c08b724ffe480ddb8f8a5ac390174925edbde0337", + "worktree/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md": "06711ca6fc5e44b64b37401a953ba2e0baa61474017fc22354fa8f1408a1be50", + "worktree/openspec/changes/isolate-semgrep-security-runtime/design.md": "f25525e37756a6caa47eb584e109199ec81c6398e60a9b4d8c9d168ee74b7f77", + "worktree/openspec/changes/isolate-semgrep-security-runtime/proposal.md": "4ee2f880701c1c91532aaa8d904e82dcaad206a2be6eb41d9badd3b6d4e9a9c4", + "worktree/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml": "f26fccaddb64ad7bcaa0ad2444c757dbce3270ba7151fb722130fc691a062d4d", + "worktree/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/corrected-mapping-review.md": "c3d245b669e5bb57fa060bcb564a1d01e7b4e5403be7ba96122661429235dedc", + "worktree/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-review-pending.md": "d8b1b927ec372065cf07c48358b546bbea6772b448ea9218ec4701fd07d56532", + "worktree/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/frozen-closure-review.md": "4908cff4a200a4747f3a0786cfb7007777ed97a54f00fc363273328b17f4da21", + "worktree/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/independent-review.md": "a981d9f787bd7dab64650254f50ebaa74cef55b79af64441c41525d7ced9692e", + "worktree/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-correction-review-evidence.json": "a79552f7d6ae2322e9fb1e6f2a2df86352a315608681a12cb9864548a0add912", + "worktree/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json": "5cdda2171b39aab9331703af3720432344d1c170ae2d2580dd652010910b9834", + "worktree/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md": "a0554d99216d85781e3f2df7d5b68142f8d51ce2933bc6e1120c5f0bb173a924", + "worktree/openspec/changes/isolate-semgrep-security-runtime/tasks.md": "0c198a3f9f166d0466332b2eb1ddeb0acc56f87809973ca02b74774d2cf4e1bb", + "worktree/pyproject.toml": "790b494893066b89f0b74608fc461a3071c4eaaa92d06ddfbb5b2d4ad2da2375", + "worktree/requirements/ci/locked.txt": "6f192da46038d0a5577d91048985f7bc351a7f8ac707cb027f19e50bc14aa4c4", + "worktree/scripts/requirements_evidence_delivery_gate.py": "fe83e438150f16798972a9a89257f904914187516fcbc269b0e13a470a701132", + "worktree/setup.py": "bb6dc23d7602e9d716c54fb6106c38f0d37769f7a51f4daf995df4bd3897760c", + "worktree/src/__init__.py": "328fb05f50f87bfa26caf37ad06de8de4a133d338c3826905e600d3ad649689c", + "worktree/src/specfact_cli/__init__.py": "1f48daba70ce569f75df5611d2884e4489cf095b88391d30d57ccf9f8eb16cce", + "worktree/tests/integration/test_distribution_build.py": "af24d9caa1259ecf9789440c516784ea01d4afb34acf8fe9ef377f5f2f46806b", + "worktree/tests/unit/scripts/test_security_audit_gate.py": "d0b1fcb16185b1908b6abc53e0c68e7baf04ede2b395492fa4d4cbb37ed8c3b9", + "worktree/tests/unit/security/test_frozen_dependency_artifacts.py": "146f45786ccb74b581c36a1a626130c540631e45fa869433d5663e56202b4215", + "worktree/tests/unit/security/test_frozen_gitpython_compatibility.py": "0d0adc56f725dd4878024c6058c341574fd80222709ff4e47ebda259b9dd5f68", + "worktree/tests/unit/security/test_frozen_jwt_compatibility.py": "35b8e0d571bf50a30599ad1634b068d2d3d37e9a46ffd45fe6dfea03771cab16", + "worktree/tests/unit/security/test_release_dependency_selection.py": "4381649df105b1bba4440dfa78deb4fd221ae78d6a013dd5db5ab3784d43c16d", + "worktree/tests/unit/security/test_release_promotion_security_gates.py": "dcf6de107fea93357421daae229bbdd144d0e592d56b28c58b0429aa4ddd9531", + "worktree/tests/unit/versioning/test_analyzer.py": "96895ff03536b953743d890d0170958c66640644867230e67ffd59b2733e97d4", + "worktree/uv.lock": "cc2b166f1facb3794d3c2fdd96b4b727631246fcc7e7fb2b1858d029742ba94b" +} +``` + +## Exact accepted selectors + +- `tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[exact-cve]`. +- `tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[exact-ghsa]`. +- `tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[expired]`. +- `tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[expiry-day]`. +- `tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[future-review]`. +- `tests/unit/security/test_release_promotion_security_gates.py::test_frozen_graph_uses_official_semgrep_with_bounded_mcp_exception`. +- `tests/unit/security/test_frozen_dependency_artifacts.py::test_frozen_certifi_bundle_excludes_compromised_root`. +- `tests/unit/security/test_frozen_jwt_compatibility.py::test_inspection_preserves_options_and_expiry_on_reuse`. +- `tests/unit/security/test_release_promotion_security_gates.py::test_patch_release_uses_next_version_in_all_sources`. +- `tests/integration/test_distribution_build.py::test_source_distribution_rebuild_preserves_bundle_mapper`. +- `tests/unit/security/test_frozen_gitpython_compatibility.py::test_submodule_update_rejects_outside_checkout_before_clone`. +- `tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[certifi-2026.10.19]`. +- `tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[gitpython-3.2.0]`. +- `tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[hatchling-1.32.4]`. +- `tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[packaging-26.3]`. +- `tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[pyjwt-2.15.1]`. +- `tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[python-discovery-1.6.1]`. +- `tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[urllib3-2.8.0]`. +- `tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[virtualenv-21.14.12]`. +- `tests/unit/security/test_release_dependency_selection.py::test_reviewed_documentation_plugins_are_locked[jekyll-feed-0.18.0]`. +- `tests/unit/security/test_release_dependency_selection.py::test_reviewed_documentation_plugins_are_locked[jekyll-redirect-from-0.17.0]`. +- `tests/unit/security/test_release_dependency_selection.py::test_reviewed_documentation_plugins_are_locked[jekyll-relative-links-0.9.1]`. +- `tests/unit/security/test_release_dependency_selection.py::test_documentation_json_has_reviewed_floor_and_major_bound`. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-review-pending.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-review-pending.md new file mode 100644 index 00000000..a6158c0e --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-review-pending.md @@ -0,0 +1,17 @@ +# Corrected mapping review pending + +The approved correction selects virtualenv 21.14.12, python-discovery 1.6.1, +and packaging 26.3. Four new cases bind exact certifi 2026.10.19, GitPython +3.2.0, PyJWT 2.15.1, and packaging 26.3 to the lock and hashed export. +The 23-case mapping awaits fresh independent technical review. This file is a +status record, not reviewer acceptance or human release authority. + +Review findings `PRRT_kwDOQLaPE86vRvLU` and `PRRT_kwDOQLaPE86vR4YF` are valid. +The first requires the exact dependency assertions. The second requires a +durable acceptance reference. The coordinator will replace the pending record +only after a fresh reviewer accepts the exact corrected mapping and report. + +The prior acceptance record is retained unchanged in +`pre-correction-review-evidence.json`. Its mutable branch URL is historical and +must not be reused as the new acceptance reference. Existing independent review +reports retain their original identities and scope. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-correction-review-evidence.json b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-correction-review-evidence.json new file mode 100644 index 00000000..7a17d155 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-correction-review-evidence.json @@ -0,0 +1,9 @@ +{ + "schema_version": "1", + "decision": "accepted", + "reviewer_id": "Codex:/root/release_probe_final_review (gpt-6.1-sol)", + "reviewer_role": "independent AI technical mapping reviewer", + "recorded_at": "2026-10-22T16:18:44.881679+00:00", + "reference": "https://github.com/nold-ai/specfact-cli/blob/hotfix/security-dependencies-0-55-11/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/frozen-closure-review.md", + "mapping_digest": "sha256:1abe2b6dc3e8a294795a00236803b002b98ad02ec05b22b2b0deb2cbf13a2020" +} diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json index 7a17d155..37299e68 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json @@ -1,9 +1,9 @@ { "schema_version": "1", "decision": "accepted", - "reviewer_id": "Codex:/root/release_probe_final_review (gpt-6.1-sol)", + "reviewer_id": "Codex:/root/release_correction_mapping_review (gpt-6.1-sol)", "reviewer_role": "independent AI technical mapping reviewer", - "recorded_at": "2026-10-22T16:18:44.881679+00:00", - "reference": "https://github.com/nold-ai/specfact-cli/blob/hotfix/security-dependencies-0-55-11/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/frozen-closure-review.md", - "mapping_digest": "sha256:1abe2b6dc3e8a294795a00236803b002b98ad02ec05b22b2b0deb2cbf13a2020" + "recorded_at": "2026-10-22T19:45:46.737707+00:00", + "reference": "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-mapping-review.md", + "mapping_digest": "sha256:9405ba32fc071ee2092077f516c4d7baf57ac0acde1575025d5545c0cbda04a8" } diff --git a/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md b/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md index bee733dd..f596f3e3 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md +++ b/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md @@ -36,7 +36,7 @@ The main release SHALL satisfy this delivery contract using the reviewed officia #### Scenario: Select the reviewed compatible tooling graph - **WHEN** the release frozen graph is resolved -- **THEN** it SHALL select Hatchling 1.32.4, urllib3 2.8.0, virtualenv 21.11.0, and python-discovery 1.6.0 +- **THEN** it SHALL select certifi 2026.10.19, GitPython 3.2.0, PyJWT 2.15.1, Hatchling 1.32.4, urllib3 2.8.0, virtualenv 21.14.12, python-discovery 1.6.1, and packaging 26.3 - **AND** the lock and hashed delivery export SHALL agree. ### Requirement: Bound the temporary official MCP advisory exception diff --git a/openspec/changes/isolate-semgrep-security-runtime/tasks.md b/openspec/changes/isolate-semgrep-security-runtime/tasks.md index 00bca80f..36a74603 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/tasks.md +++ b/openspec/changes/isolate-semgrep-security-runtime/tasks.md @@ -1,14 +1,16 @@ # Main release tasks -- [ ] Validate this focused scope and independently accept the scenario mapping. +- [x] Record the owner-approved correction to virtualenv 21.14.12, python-discovery 1.6.1, and packaging 26.3. +- [ ] Independently accept the corrected 23-case mapping, including exact certifi, GitPython, PyJWT, and packaging selections. +- [ ] Retain the accepted review with a repository-relative or immutable reference. - [ ] Commit test-authored content with all local hooks and retain native failing CI evidence. - [ ] Freeze mappings, selected tests, ancestor conftest files, and transitive Python imports. -- [ ] Refresh the eight reviewed official Python packages and both frozen files. +- [ ] Refresh the nine reviewed official Python packages and both frozen files. - [ ] Add only the exact approved MCP advisory exception. - [ ] Update documentation dependency declarations and lock, then execute real gem compatibility probes. - [ ] After native RED acceptance, port the reviewed dev Ruby compatibility probe to `scripts/check_jekyll_dependencies.rb` and its CI workflow to `.github/workflows/docs-dependency-checks.yml`. - [ ] Add the missing bundle-mapper source-distribution inclusion and synchronize version 0.55.11 with the changelog. - [ ] Verify published runtime equality, strict module signatures, both audit lanes, and required local and CI gates. - [ ] Complete independent final review and present the unmerged PR. -- [ ] Update the internal wiki mirror and rebuild its graph through the coordinating agent. +- [x] Update the internal wiki mirror and rebuild its graph through the coordinating agent. - [ ] Archive with the native OpenSpec command only after the release change is complete. diff --git a/tests/unit/security/test_release_dependency_selection.py b/tests/unit/security/test_release_dependency_selection.py index 0c06c564..9946dd12 100644 --- a/tests/unit/security/test_release_dependency_selection.py +++ b/tests/unit/security/test_release_dependency_selection.py @@ -12,7 +12,16 @@ @pytest.mark.parametrize( ("name", "version"), - [("hatchling", "1.32.4"), ("urllib3", "2.8.0"), ("virtualenv", "21.11.0"), ("python-discovery", "1.6.0")], + [ + ("certifi", "2026.10.19"), + ("gitpython", "3.2.0"), + ("pyjwt", "2.15.1"), + ("hatchling", "1.32.4"), + ("urllib3", "2.8.0"), + ("virtualenv", "21.14.12"), + ("python-discovery", "1.6.1"), + ("packaging", "26.3"), + ], ) def test_reviewed_tooling_versions_match_frozen_export(name: str, version: str) -> None: lock = tomllib.loads((REPO_ROOT / "uv.lock").read_text(encoding="utf-8")) diff --git a/tests/unit/versioning/test_analyzer.py b/tests/unit/versioning/test_analyzer.py index 02541d0e..ac95a38b 100644 --- a/tests/unit/versioning/test_analyzer.py +++ b/tests/unit/versioning/test_analyzer.py @@ -48,7 +48,7 @@ def _create_sample_bundle(base_path: Path, bundle_name: str = "test-bundle") -> def _init_repo(base_path: Path) -> Repo: repo = Repo.init(base_path) - repo.index.add([str(p) for p in base_path.rglob("*") if p.is_file()]) + repo.git.add("--all") repo.index.commit("init") return repo From cd9a1b7953c88586d8d90fe526627b73e63265a8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:54:05 +0000 Subject: [PATCH 5/7] test(security): bind advisory correction Stage: prefix7 Source: fe1e1e9c0ce77ee0b3b620a882c77b0be8501860 Ordinal: 5 Manifest: 6c3d344ecb69beca59231c3c49ae05dba50790fd253db87f37122028de1dfa2a Executor: bd639eb85d78f5a3d3438893297b31f5b3a93e88 Run: 39949294200 Attempt: 1 --- .../CHANGE_VALIDATION.md | 13 +- .../TDD_EVIDENCE.md | 172 ++++++++++++++++++ .../design.md | 29 ++- .../proposal.md | 30 ++- .../requirements-evidence.yaml | 102 ++++++++++- .../advisory-correction-review-pending.md | 21 +++ .../advisory-mapping-review.md | 91 +++++++++ .../correction-review-pending.md | 13 +- .../pre-advisory-review-evidence.json | 9 + .../requirements-proof/review-evidence.json | 8 +- .../specs/isolated-scanner-runtime/spec.md | 26 ++- .../isolate-semgrep-security-runtime/tasks.md | 36 +++- .../scripts/test_runtime_discovery_smoke.py | 38 +++- .../scripts/test_check_version_sources.py | 12 +- .../scripts/test_reproducible_delivery.py | 4 +- .../unit/scripts/test_security_audit_gate.py | 98 ++++++++-- .../test_frozen_gitpython_compatibility.py | 83 +++++++++ .../test_release_dependency_selection.py | 2 +- .../test_release_promotion_security_gates.py | 41 +++-- 19 files changed, 753 insertions(+), 75 deletions(-) create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-mapping-review.md create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-advisory-review-evidence.json diff --git a/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md b/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md index 13140762..d7d97309 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md +++ b/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md @@ -5,10 +5,17 @@ worktree. This phase implements issue #780 release preparation, with parent #747 The coordinating agent verified Todo status, labels, project, and hierarchy. The dev change was read as context. Its admission artifacts and dev-only CI changes are excluded. Genuine main native RED is required before production edits. -No independently accepted mapping or passing evidence is claimed yet. +That initial status is historical. The 23-case correction was subsequently accepted +and authenticated native RED ran at 8cce44f449e9559d0100064d629c81991784ab41. The owner explicitly approved the virtualenv correction on 2026-10-22. The coordinator rechecked issue #780 as OPEN/Todo with labels, project, and hierarchy intact, and updated the internal wiki mirror and rebuilt its graph. The corrected -release selects nine changed Python packages relative to main. Fresh independent -acceptance and native RED remain pending for the changed specification and tests. +release selects nine changed Python packages relative to main. That correction received independent acceptance and native RED. + +The owner separately approved GitPython 3.2.1 and the exact opentelemetry-api +1.37.0 exception on 2026-10-22. Fresh independent mapping acceptance for this new correction is recorded in +requirements-proof/advisory-mapping-review.md. Only new native RED remains pending. All twelve saved production paths +were verified and backed up before restoration to HEAD. The four independently +reviewed fixture corrections are retained unchanged. The coordinating agent owns +the matching internal wiki update and has reported its graph rebuild complete. diff --git a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md index d95a3268..638499fb 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md +++ b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md @@ -2,6 +2,22 @@ ## Current proof status +The prior 23-case correction received independent mapping acceptance and native +RED at commit 8cce44f449e9559d0100064d629c81991784ab41, run 39843165302 attempt 1. +Its accepted receipt is preserved in `requirements-proof/pre-advisory-review-evidence.json`. +The subsequent local production results below remain authentic history. + +The owner has now approved GitPython 3.2.1 and the separate exact OpenTelemetry +exception. Production inputs have been restored before specification and test +changes. Fresh independent acceptance for the 31-case advisory correction is recorded in +`requirements-proof/advisory-mapping-review.md` on 2026-10-23. Only new native RED +remains pending. The active receipt uses that repository-relative accepted report. No earlier proof is reused to approve changed test or mapping bytes. + +## Superseded 23-case pending note + +This note records the state before the 23-case acceptance and native RED above. +Its pending claims are historical and do not describe the current 23-case status. + The owner-approved correction now selects 23 cases. It changes virtualenv to 21.14.12 and python-discovery to 1.6.1, adds packaging 26.3, and binds the exact certifi 2026.10.19, GitPython 3.2.0, and PyJWT 2.15.1 selections. The artifact @@ -267,3 +283,159 @@ The plan identity digest is `sha256:29af99fc48f7afd6a0dc641716e9ccb5c8bf230ef290 The sibling raw logs, argument arrays, and `failure-diagnostics.json` preserve every case's actual failure message. The artifact paths are relative to `/private/tmp/specfact-release-20261022/`. + +## Corrected production verification on 2026-10-22 + +The coordinator authenticated native RED run 39843165302 attempt 1, artifact +12453832458, for HEAD 8cce44f449e9559d0100064d629c81991784ab41 and tree +a3cfdc441190e11a0c4db22cbdc324b44a2b7f8e. All 23 selected cases failed with +no errors or skips. The record is retained at +`/private/tmp/specfact-release-20261022/correction-tests/native-ready.json`. +Production changes began only after that acceptance. All 406 frozen source, +test, mapping and review hashes still match after the verification below. + +The normal targeted resolver selected virtualenv 21.14.12, python-discovery +1.6.1 and packaging 26.3. The complete graph differs from main by the nine +approved third-party updates and the core 0.55.11 version, with no package +names added or removed. The canonical export, hash-enforced environment sync, +canonical refresh, frozen parity and `uv lock --check` passed. + +Fresh local GREEN passed all 23 selected cases and all seven complete files, +68 tests, with no skips or errors. The source distribution rebuilt offline +into the real wheel. Strict signatures passed for all four unchanged module +manifests. The coordinator independently compared 295 payload members and +297 guarded source files with published 0.55.4 and accepted only the authorized +version and metadata differences. Wheel SHA256 remains +`a0ca37c5030d88d33c93286aabd1e6fe27c28224031a0b7dd4cc7d9ffa8fc709`. + +Normal virtualenv creation and hash-enforced installation of the built wheel +passed. Installed-wheel GitOperations, ChangeAnalyzer and CodeAnalyzer probes +passed. Official Semgrep 1.179.0 returned one positive finding with exit 1 and +zero negative findings with exit 0, both without scanner errors. The 29 analyzer +tests passed. The separate Code Review frozen audit passed. License, Bandit, +dependency trust, version parity, strict PyPI-ahead, frozen Node install, format, +type-check, lint and YAML checks passed. BasedPyright reported zero errors and +1525 warnings. The normal contract command exited zero through its +no-modified-files path; it did not execute contract scenarios. + +The primary strict audit failed on GitPython 3.2.0 CVE-2026-114145 alias +GHSA-83vg-56qc-22m7 and opentelemetry-api 1.37.0 CVE-2026-114603 alias +GHSA-xcq7-p3pf-p88h. Only the previously approved exact MCP advisory was waived. +No additional exception, target update or suppression was made. + +The full smart-test run collected 3252 cases and finished with 3212 passed, +29 failed, 10 skipped and one setup error in 203.282 seconds. Post-run inspection +found 37 installed distributions differing from the frozen versions. Beartype +and Semgrep installation timestamps fall inside the test run. Twenty-six +failures or errors involve inconsistent Beartype imports. The cause of these +mid-run installations remains unverified, so the run is not accepted as stable +frozen-environment compatibility evidence. Four other failures cover the old +JSON floor literal, the old Hatchling backend literal, and two staged-version +follow-up fixtures. All frozen tests remain unchanged. The private environment +was restored using the unchanged hash-pinned export and final version parity +confirmed. Repository SAST was then repeated with frozen Semgrep 1.179.0 and +passed with zero findings against the zero-finding baseline. + +The earlier eight Ruby probes and both root/prefix documentation builds remain +applicable because dependency and workflow bytes match and the Ruby probe only +loses two narration comments. The prior private macOS-platform adaptation is +retained as evidence. Actual Linux frozen Bundler CI is still required. + +All fresh commands, exit codes, JUnit results, failure diagnostics and hashes are +retained under `/private/tmp/specfact-release-20261022/correction-green/`. +`results.jsonl` is the command ledger and `checkpoint.md` records the handoff. +The two strict-audit findings and full-suite failures block release readiness. +No staging, commit, push, signing, merge, publication or archive occurred. + +## Advisory correction local RED on 2026-10-23 + +The owner approved GitPython 3.2.1 and the separate exact opentelemetry-api 1.37.0 +exception on 2026-10-22. This test-only correction first verified and backed up +all twelve saved production paths, then restored them to HEAD. The four reviewed +fixture files remain byte-identical to their accepted checkpoint. No environment +synchronization, production adoption, staging, commit, push, or signing occurred. + +The new plan selects 31 cases. Its unchanged host-access run reports **31 failed, +0 passed, 0 errors, 0 skipped**. The supplementary run reports **23 passed, +0 failed, 0 errors, 0 skipped**. Both ran with the restored main source inputs. +The shared interpreter retains its existing tooling environment; the dependency +behavior probes separately install the exact hash-pinned frozen delivery wheels. + +All three new Git metadata paths reached the mocked clone boundary exactly once +with no ValueError under frozen GitPython 3.1.61. Hook contents remained unchanged, +and neither `.gitmodules` nor module metadata was created. Ordinary `vendor/module` +and `.github/module` reached the same boundary once and passed. The original +`../outside` update probe remains unchanged and failed at its clone sentinel. +No unsafe repository was cloned and no active hook was created. + +The empty restored exception register rejects the six MCP/OpenTelemetry exact-ID +and expiry-day cases. The four expired/future cases reject the finding but lack +the required invalid-record diagnostic. These are real assertion failures. +Wrong package/version, unrelated/mixed advisories, skipped dependencies and the +missing-entry control pass. Ordinary MCP evaluations use 2026-10-22; its record +still requires the original 2026-10-21 review date. Future/expiry controls remain. + +The remaining failures show the compromised certifi root, seven mutated JWT option +keys, the absent source bundle-mapper inclusion, old package selections, old docs +gem selections/JSON floor, and old core version. Every actual failure message and +traceback is retained in `advisory-correction-tests/failure-diagnostics.json`. + +The first sandbox run failed official wheel downloads because DNS was blocked. +Its `sandbox-*.log/xml` files remain limitation evidence and are excluded from +behavioral RED. The unchanged commands passed artifact installation with host +network access. Exact pytest argv, times and exits are in `mapped-command.json`, +`controls-command.json`, `mapped-exit.json` and `controls-exit.json` under +`/private/tmp/specfact-release-20261022/advisory-correction-tests/`. + +The released canonical planning command uses the exact base and planned maturity: + +```sh +. /private/tmp/specfact-release-20261022/red-owner/env.sh +export GITHUB_BASE_REF=main +python scripts/requirements_evidence_delivery_gate.py \ + --base-ref 5262efb0881e4fac7697d629b0530250342bc656 \ + --required-maturity planned \ + --output /private/tmp/specfact-release-20261022/advisory-correction-tests/planning-final.json \ + --summary /private/tmp/specfact-release-20261022/advisory-correction-tests/planning-final.md \ + --plan-output /private/tmp/specfact-release-20261022/advisory-correction-tests/plan-final.json +``` + +The source mapping digest is `sha256:0788c6326b39842e80a8cf4e308ac8508c754c384d9e6479b5cd61f0ca8b1c1d`. +The combined mapping digest is `sha256:d4d5c52f4194122dd0745e808dd29d9e19104cc704256c5c66414c3d247d0dbd`. +The plan identity digest is `sha256:59785c8bb37070043e729a0d06c63ce1841f39c4631038fa010e9fc7d8437a69`. +`advisory-correction-tests/mapped.xml` SHA256 is +`e39cc98cc217500edd23697c97b05de9a2306c3acf391fdd845d99585eb9a425`. +`advisory-correction-tests/controls.xml` SHA256 is +`4b568ffc2679d0712f1d51f878ca09d9b5a6eda2cceddfc7d2275a9bee70616b`. + +Ruff formatting/checks, Markdown auto-fix/lint, strict OpenSpec validation and +`git diff --check` pass. These local results require fresh independent mapping +acceptance and native RED before any production adoption. Root owns that review, +the normal test-only commit and hooks, native evidence, and the internal wiki. +The coordinator reports the wiki update and graph rebuild complete. The original +23-case accepted proof remains intact and applies only to its original bytes. + +## Advisory correction independent acceptance on 2026-10-23 + +The independent AI technical review accepted the exact 31-case mapping and +reviewed test/proposal bytes. Its durable report is +`requirements-proof/advisory-mapping-review.md`, SHA256 +`b5ff286ab72e9702bf26fd70ba423bfcb5255e8c956977c07ab0458152aa358e`. +The active receipt records AI technical acceptance without human authority. +The coordinator reconciled every active status tracker to acceptance complete +and native RED pending. Earlier pending notes remain explicitly historical. + +The coordinator separately executed the same safe create-submodule probe on +the installed former candidate GitPython 3.2.0. All three metadata representations +reached the mocked clone once without rejection; ordinary paths did likewise. +Hooks were unchanged and no module/config artifacts were created. This is +supplemental reproduction of CVE-2026-114145 on 3.2.0, separate from baseline +3.1.61 native-proof preparation. The managed JSON evidence SHA256 is +`154d18696b02b3d8ca47b1af83b341685fad583cac8e37141038626abfd00f09`. +No real unsafe clone or active hook was used. Native RED remains required. + +The first normal test-only commit attempt correctly failed the docs security +hook because the updated JSON assertion expected its matching Gemfile change. +That one reviewed fixture is retained locally for the production commit with +the Gemfile. The 31 mapped tests, selectors and accepted proof bytes are +unchanged. No hook exception, skip or production adoption was used. diff --git a/openspec/changes/isolate-semgrep-security-runtime/design.md b/openspec/changes/isolate-semgrep-security-runtime/design.md index b0f2173d..db05994c 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/design.md +++ b/openspec/changes/isolate-semgrep-security-runtime/design.md @@ -47,8 +47,7 @@ artifact and negative audit controls remain unchanged in their test files and in the normal required pytest suite. They are supplementary safety checks, not new behavior needing failure-before-fix provenance. The newly approved dependency correction expands the plan to 23 cases. It adds exact certifi, GitPython, PyJWT, and packaging lock/export selections -and updates the virtualenv and python-discovery expectations. Final verification -runs all 23 selected cases and all fifteen supplementary controls. +and updates the virtualenv and python-discovery expectations. That correction verified all 23 selected cases and all fifteen supplementary controls. No artifact behavior assertion, negative control, or native policy is weakened. ## Corrected dependency selection and review boundary @@ -60,7 +59,29 @@ frozen certifi, GitPython, and PyJWT wheels. Exact-version assertions complement those behavior checks. The prior accepted 19-case proof applies only to its original test and mapping -bytes. Keep its reports as history. A new acceptance record remains pending -until independent review of the corrected mapping, and its reference must be +bytes. Keep its reports as history. That corrected mapping was independently accepted before native RED. Any subsequent +changed mapping requires fresh independent acceptance, and its reference must be repository-relative or pinned to an immutable commit. The coordinator must verify fresh native RED before restoring the saved production patch. + +## Advisory correction boundary + +The checkout probe adds a path-scenario table around the existing full three-package +hash-pinned wheel extraction. A dedicated sentinel replaces `Submodule._clone_repo`. +`Repo.create_submodule` must raise ValueError before that sentinel for Git metadata +paths. The probe records clone calls and compares inert hook bytes before and after. +It creates no malicious remote repository or executable hook. Passing ordinary-path +controls stay outside the native RED mapping. Preserve the existing `../outside` +update probe unchanged. + +The audit scenario table covers the committed register through the existing strict +gate. Ordinary MCP evaluations move to 2026-10-22 because the whole register includes +the new OpenTelemetry review date. MCP's own 2026-10-21 record and true future and +expiry tests remain unchanged. The scanner graph contract now requires exactly +two separate records and official opentelemetry-api 1.37.0. No gate code changes. + +The accepted 23-case receipt is preserved as `pre-advisory-review-evidence.json`. +The active receipt records fresh independent AI technical acceptance in +`requirements-proof/advisory-mapping-review.md`; new native RED remains pending. The new +plan includes only observed baseline failures. Supplementary controls must pass. +Root owns independent acceptance, native RED, wiki synchronization, and production. diff --git a/openspec/changes/isolate-semgrep-security-runtime/proposal.md b/openspec/changes/isolate-semgrep-security-runtime/proposal.md index 0a9ca1e2..242895f1 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/proposal.md +++ b/openspec/changes/isolate-semgrep-security-runtime/proposal.md @@ -4,17 +4,18 @@ The published v0.55.4 runtime must receive the reviewed dependency repairs without unreleased dev runtime changes. This is the main release phase of issue #780. -The owner authorized version 0.55.11 and an exact MCP exception through +The owner authorized version 0.55.11 and exact MCP and OpenTelemetry exceptions through 2026-11-04 inclusive. The previous dev admission work supplies no native RED proof for this main branch. ## What Changes -- Refresh official Python packages to certifi 2026.10.19, GitPython 3.2.0, +- Refresh official Python packages to certifi 2026.10.19, GitPython 3.2.1, Hatchling 1.32.4, PyJWT 2.15.1, urllib3 2.8.0, virtualenv 21.14.12, python-discovery 1.6.1, packaging 26.3, and Semgrep 1.179.0. Preserve MCP 1.29.0. -- Bound the existing advisory register to MCP 1.29.0 and its reviewed advisory, - with review date 2026-10-21 and expiry 2026-11-04 inclusive. +- Bound the advisory register to two separate exact records: MCP 1.29.0 reviewed + 2026-10-21 and opentelemetry-api 1.37.0 reviewed 2026-10-22. Both expire + 2026-11-04 inclusive and retain only their approved advisory IDs. - Refresh documentation gems to jekyll-feed 0.18.0, jekyll-redirect-from 0.17.0, and jekyll-relative-links 0.9.1. Require JSON >=2.21.2,<3 and verify the installed gem behavior separately. @@ -53,9 +54,9 @@ outside this release. No local signing, merge, or publication is authorized. - The lock and hashed export select every exact reviewed Python version. - The exact frozen certificate wheel excludes the compromised emSign root. -- The exact GitPython wheel rejects an outside submodule path before cloning. +- The exact GitPython wheel rejects outside paths and Git metadata checkout paths before cloning. - The exact PyJWT wheel preserves caller options and expiry verification on reuse. -- The only waived advisory is the exact approved MCP finding inside its dates. +- Only the two exact approved MCP and OpenTelemetry findings are waived inside their separate review windows. - A real offline source-to-wheel build includes the unchanged bundled manifest. - Native CI retains genuine test-first failures before dependency or release edits. - Required quality checks, independent review, strict signatures, and dependency audits pass before the PR is offered for merge. @@ -65,7 +66,22 @@ outside this release. No local signing, merge, or publication is authorized. The owner approved replacing virtualenv 21.11.0 after the strict audit reported five additional advisories. The official virtualenv 21.14.12 wheel requires python-discovery >=1.6.1 and packaging >=26.3. The corrected selections are -21.14.12, 1.6.1, and 26.3. All other reviewed targets remain unchanged. +21.14.12, 1.6.1, and 26.3. The other targets remained unchanged in that correction. The retained scratch resolution changes only these three packages relative to the prior candidate, with no added or removed packages. Fresh independent mapping acceptance and native RED are required before applying production edits. + +## Approved advisory correction on 2026-10-22 + +The owner approved official GitPython 3.2.1 after the audit reported +CVE-2026-114145 / GHSA-83vg-56qc-22m7 in 3.2.0. The frozen wheel must reject +`.git/hooks` and `GIT~1/hooks` checkout destinations before cloning. Ordinary +`vendor/module` and `.github/module` paths must still reach the clone boundary. + +The owner separately approved opentelemetry-api 1.37.0 CVE-2026-114603 and alias +GHSA-xcq7-p3pf-p88h from 2026-10-22 through 2026-11-04 inclusive. The mitigation +permits ordinary local-rule CLI scanning only and excludes an instrumented HTTP +service accepting untrusted propagation headers. Preserve MCP 1.29.0 and its +original IDs and 2026-10-21 review date. Each record remains exact and expiring. +The approved scope requires fresh mapping acceptance and native RED before +production adoption. The earlier 23-case acceptance and native RED remain history. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml b/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml index a67ac634..780ba844 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml @@ -78,11 +78,11 @@ requirements: - case_id: GITPYTHON-FROZEN-SELECTION scenario_id: select-the-reviewed-compatible-tooling-graph method: test - intent: Check reviewed gitpython 3.2.0 selection and export parity. + intent: Check reviewed gitpython 3.2.1 selection and export parity. observable: The official registry lock version and exported requirement both match the reviewed version. selector: runner: pytest - node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[gitpython-3.2.0] + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[gitpython-3.2.1] - case_id: PYJWT-FROZEN-SELECTION scenario_id: select-the-reviewed-compatible-tooling-graph method: test @@ -132,10 +132,10 @@ requirements: runner: pytest node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[python-discovery-1.6.1] openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:bound-the-temporary-official-mcp-advisory-exception: - rationale: Keep both main audit lanes strict with one exact, expiring owner-approved exception. + rationale: Keep both main audit lanes strict with separate exact, expiring owner-approved exceptions. stakeholder_refs: - nold-ai/specfact-cli#780 - touchpoints: + touchpoints: &id005 - id: touchpoint-1 kind: config_file locator: pyproject.toml @@ -159,7 +159,8 @@ requirements: scenario_id: bound-the-temporary-official-mcp-advisory-exception method: test intent: Bind the official scanner graph and exact advisory record. - observable: Only official Semgrep 1.179.0 and MCP 1.29.0 occur with the approved exact exception. + observable: Official Semgrep 1.179.0, MCP 1.29.0 and opentelemetry-api 1.37.0 occur with exactly the two separate approved + records. selector: runner: pytest node_id: tests/unit/security/test_release_promotion_security_gates.py::test_frozen_graph_uses_official_semgrep_with_bounded_mcp_exception @@ -170,7 +171,7 @@ requirements: observable: Only the approved package, version, and advisory inside the inclusive date window is waived. selector: runner: pytest - node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[exact-cve] + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[exact-cve] - case_id: MCP-EXACT-GHSA scenario_id: bound-the-temporary-official-mcp-advisory-exception method: test @@ -178,7 +179,7 @@ requirements: observable: Only the approved package, version, and advisory inside the inclusive date window is waived. selector: runner: pytest - node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[exact-ghsa] + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[exact-ghsa] - case_id: MCP-EXPIRY-DAY scenario_id: bound-the-temporary-official-mcp-advisory-exception method: test @@ -186,7 +187,7 @@ requirements: observable: Only the approved package, version, and advisory inside the inclusive date window is waived. selector: runner: pytest - node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[expiry-day] + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[expiry-day] - case_id: MCP-EXPIRED scenario_id: bound-the-temporary-official-mcp-advisory-exception method: test @@ -194,7 +195,7 @@ requirements: observable: Only the approved package, version, and advisory inside the inclusive date window is waived. selector: runner: pytest - node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[expired] + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[expired] - case_id: MCP-FUTURE-REVIEW scenario_id: bound-the-temporary-official-mcp-advisory-exception method: test @@ -202,7 +203,7 @@ requirements: observable: Only the approved package, version, and advisory inside the inclusive date window is waived. selector: runner: pytest - node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_mcp_exception_is_exact_and_time_bounded[future-review] + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[future-review] ? openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:rebuild-the-source-distribution-with-its-bundled-manifest : rationale: Exercise the actual standard source-to-wheel delivery path. stakeholder_refs: &id003 @@ -303,3 +304,84 @@ requirements: selector: runner: pytest node_id: tests/unit/security/test_release_promotion_security_gates.py::test_patch_release_uses_next_version_in_all_sources + ? openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:reject-submodule-checkouts-into-git-metadata-before-cloning + : rationale: Observe rejection before the clone boundary without cloning unsafe repositories. + stakeholder_refs: + - nold-ai/specfact-cli#780 + touchpoints: *id002 + verification_cases: + - case_id: GITPYTHON-CREATE-GIT-METADATA + scenario_id: reject-submodule-checkouts-into-git-metadata-before-cloning + method: test + intent: Exercise frozen Repo.create_submodule with a mocked clone sentinel. + observable: ValueError occurs before clone; hooks stay unchanged and neither .gitmodules nor module metadata is created. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_gitpython_compatibility.py::test_submodule_creation_validates_checkout_before_clone[git-metadata] + - case_id: GITPYTHON-CREATE-GIT-SHORT-NAME + scenario_id: reject-submodule-checkouts-into-git-metadata-before-cloning + method: test + intent: Exercise frozen Repo.create_submodule with a mocked clone sentinel. + observable: ValueError occurs before clone; hooks stay unchanged and neither .gitmodules nor module metadata is created. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_gitpython_compatibility.py::test_submodule_creation_validates_checkout_before_clone[git-short-name] + - case_id: GITPYTHON-CREATE-ABSOLUTE-METADATA + scenario_id: reject-submodule-checkouts-into-git-metadata-before-cloning + method: test + intent: Exercise frozen Repo.create_submodule with a mocked clone sentinel. + observable: ValueError occurs before clone; hooks stay unchanged and neither .gitmodules nor module metadata is created. + selector: + runner: pytest + node_id: tests/unit/security/test_frozen_gitpython_compatibility.py::test_submodule_creation_validates_checkout_before_clone[absolute-metadata] + openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:bound-the-separate-opentelemetry-advisory-exception: + rationale: Keep the exact OpenTelemetry waiver separate from the unchanged MCP record. + stakeholder_refs: + - nold-ai/specfact-cli#780 + touchpoints: *id005 + verification_cases: + - case_id: OTEL-EXACT-CVE + scenario_id: bound-the-separate-opentelemetry-advisory-exception + method: test + intent: Exercise the committed exact OpenTelemetry record through the strict audit gate. + observable: Only the approved package, version and advisory inside the inclusive review and expiry window is waived; + invalid dates fail closed. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-exact-cve] + - case_id: OTEL-EXACT-GHSA + scenario_id: bound-the-separate-opentelemetry-advisory-exception + method: test + intent: Exercise the committed exact OpenTelemetry record through the strict audit gate. + observable: Only the approved package, version and advisory inside the inclusive review and expiry window is waived; + invalid dates fail closed. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-exact-ghsa] + - case_id: OTEL-EXPIRY-DAY + scenario_id: bound-the-separate-opentelemetry-advisory-exception + method: test + intent: Exercise the committed exact OpenTelemetry record through the strict audit gate. + observable: Only the approved package, version and advisory inside the inclusive review and expiry window is waived; + invalid dates fail closed. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-expiry-day] + - case_id: OTEL-EXPIRED + scenario_id: bound-the-separate-opentelemetry-advisory-exception + method: test + intent: Exercise the committed exact OpenTelemetry record through the strict audit gate. + observable: Only the approved package, version and advisory inside the inclusive review and expiry window is waived; + invalid dates fail closed. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-expired] + - case_id: OTEL-FUTURE-REVIEW + scenario_id: bound-the-separate-opentelemetry-advisory-exception + method: test + intent: Exercise the committed exact OpenTelemetry record through the strict audit gate. + observable: Only the approved package, version and advisory inside the inclusive review and expiry window is waived; + invalid dates fail closed. + selector: + runner: pytest + node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-future-review] diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md new file mode 100644 index 00000000..4c448069 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md @@ -0,0 +1,21 @@ +# Historical advisory correction pending note + +The pending note below is superseded by accepted independent AI technical +review in `advisory-mapping-review.md` on 2026-10-23. The active receipt records +that acceptance. Only new authenticated native RED remains pending before +production adoption. This does not grant human, merge or publication authority. + +## Original pending note + +### Advisory correction review pending + +GitPython 3.2.1, rejection of Git metadata checkout paths before cloning, and the +separate exact opentelemetry-api 1.37.0 exception require fresh independent mapping +review and native RED. This status grants no acceptance or production authority. +The prior accepted 23-case receipt remains in pre-advisory-review-evidence.json. +Its authentic native RED and local GREEN apply only to the original proof bytes. + +The 31 selected cases failed locally for their expected assertions on restored +main inputs, while all 23 supplementary controls passed. The released exact-base +planning command passes at planned maturity. These local observations do not +supply independent acceptance or native CI provenance. Fresh review is pending. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-mapping-review.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-mapping-review.md new file mode 100644 index 00000000..9343fa43 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-mapping-review.md @@ -0,0 +1,91 @@ +# Advisory correction independent technical mapping review + +TL;DR. Accepted for the exact test-only specification, mapping and source bytes identified below. No blocking technical finding. This is independent AI technical mapping acceptance by Codex agent `/root/release_advisory_mapping_review`. It grants no human approval, native CI acceptance, production adoption, signing, merge or publication authority. + +Reviewed on 2026-10-23 Europe/Berlin in `/private/tmp/specfact-release-20261022/worktrees/hotfix/security-dependencies-0-55-11`, branch `hotfix/security-dependencies-0-55-11`, HEAD `8cce44f449e9559d0100064d629c81991784ab41`. The exact main base is `5262efb0881e4fac7697d629b0530250342bc656`. I did not author these corrections. I read the actual current diff, specification/proposal/design, mapping, four changed security/audit test files, retained frozen probes, failure messages and tracebacks, both JUnit documents, plan, statuses and receipts. The former mapping report supplied history only. I made no repository edit, test-suite run, installation, environment sync, staging, commit, push or external change. The only executed runtime verification was the expressly permitted unchanged canonical planning command, sourced through `red-owner/env.sh` with `GITHUB_BASE_REF=main`, writing reviewer outputs outside the repository. Throughput checkpoint is n/a for this read-only investigation. No new agents were spawned under the task restriction. + +The fresh released canonical command exited zero at planned maturity. `advisory-review-plan.json` equals the supplied `advisory-correction-tests/plan-final.json` as parsed data. Source mapping digest is `sha256:0788c6326b39842e80a8cf4e308ac8508c754c384d9e6479b5cd61f0ca8b1c1d`. Combined mapping digest is `sha256:d4d5c52f4194122dd0745e808dd29d9e19104cc704256c5c66414c3d247d0dbd`. Plan identity digest is `sha256:59785c8bb37070043e729a0d06c63ce1841f39c4631038fa010e9fc7d8437a69`. Embedded combined plan digest is `sha256:597f3228ceb13ee190bed760bae90988af631d4f0714105077f446291cfdd438`. The reviewed proposal SHA256 is `a2f7551d578a32c17eebb7fa057ac3f4717f8bd05378cf89380e64f4bd94a824`. + +All 19 current checkpoint entries match actual worktree bytes. The four separate fixture hashes match `fixture-fix/checkpoint-sha256.json`; those fixtures were checked for preservation only. The ten tracked production paths are byte-identical to both HEAD and the exact main base. The two new Ruby-related production paths are absent. The index is empty. No changed production implementation, audit gate, dependency export, lock, workflow, runtime, module signing asset, ancestor conftest or Python helper is hidden in the current correction. The existing Git requirement extractor and outside-update probe remain unchanged. + +The mapping has 31 distinct selectors across ten requirement/scenario pairs. Its selector set equals `selectors.json`, the recorded mapped command and the actual JUnit case identities. The observed mapped run has 31 failures, zero errors, zero skips and zero passes. Its SHA256 is `e39cc98cc217500edd23697c97b05de9a2306c3acf391fdd845d99585eb9a425`. The 31 failure-diagnostic entries agree with the JUnit failures. These are actual path, behavior, selection and date-diagnostic disagreements. They are not DNS counts or an inference from a nonzero exit. + +The added Git cases run the frozen export's complete GitPython/gitdb/smmap closure with exact pins, supplied hashes, `--require-hashes`, `--no-deps`, binary wheels, `--no-config` and the official index. The isolated interpreter has `-I -S` and imports GitPython from the installed target. `Repo.create_submodule` is the exercised entry point. The dedicated clone exception is caught separately from ValueError. For `.git/hooks`, `GIT~1/hooks` and absolute in-checkout `.git/hooks`, the baseline mismatches are `rejected=false`, `boundary_reached=true` and `clone_calls=1`, rather than the required pre-clone rejection. The result comparison also requires unchanged inert hook bytes, no `.gitmodules` and no module metadata. No malicious remote repository or active hook is created. Both `vendor/module` and `.github/module` controls pass by reaching that same sentinel once without ValueError. The retained `../outside` update case fails specifically because its unchanged clone sentinel is reached. The exact-selection test independently requires official GitPython 3.2.1 and lock/export parity. This is baseline 3.1.61 evidence; it does not claim executed 3.2.0 or 3.2.1 GREEN compatibility. + +The audit scenarios exercise the unchanged strict gate with the committed register, not an invented accepted record. OpenTelemetry exact CVE, exact GHSA and inclusive 2026-11-04 acceptance fail against the empty main register. Its future-review 2026-10-21 and expired 2026-11-05 cases fail because the required invalid-record diagnostic is absent. MCP has the equivalent five mapped date/ID cases. Ordinary MCP evaluation uses 2026-10-22 so the unchanged whole-register date guard can accept a valid OpenTelemetry record; MCP's actual expected record remains reviewed on 2026-10-21. Its original 2026-10-20 future and 2026-11-05 expired boundaries are preserved. The graph test requires exactly two separate records, exact approved IDs, versions and dates, nonempty rationale/mitigation, official opentelemetry-api 1.37.0 in lock/export, official Semgrep 1.179.0 and MCP 1.29.0, and the unchanged minimum-version policy. The global date guard and strict skipped-dependency handling remain intact. + +The other actual failures retain the exact compromised emSign fingerprint, seven extra caller JWT option keys, an offline source-to-wheel backend error for absent `modules/bundle-mapper`, eight older frozen package selections, three old documentation plugin selections, the old JSON floor and version 0.55.4. Early failing assertions leave later assertions for GREEN, including the graph's exact records and official origins. Full mapped files must therefore run during final GREEN verification. + +The supplementary run has 23 passes and zero failures/errors/skips. Its SHA256 is `4b568ffc2679d0712f1d51f878ca09d9b5a6eda2cceddfc7d2275a9bee70616b`. Its actual case identities equal the recorded control command and are disjoint from the RED mapping. Eleven hostile artifact/hash/origin controls, four retained MCP rejection controls, two ordinary Git checkout controls and six OpenTelemetry rejection controls are present. The latter cover wrong package/version, unrelated/mixed advisories, skipped dependency and missing entry. Their current passes cannot prove they stay strict after records are added, so the full controls remain required for GREEN. The sandbox XML/logs are limitation history only. The host-enabled mapped/control XML, full tracebacks and exact command/exit records supply the local evidence reviewed here. + +The preserved `pre-advisory-review-evidence.json` equals HEAD's old accepted receipt byte-for-byte and binds the old source digest. Current TDD, validation, tasks and pending-status records distinguish accepted historical 23-case proof from the new pending 31-case correction. Historical pending text is explicitly marked superseded. The active receipt honestly has `decision=pending` and a repository-relative new pending-note reference. This report allows the coordinator to record fresh AI technical acceptance for these bytes and reconcile active trackers to acceptance with native RED pending. The prior native run is historical evidence here; I did not independently retrieve its remote provenance in this review. No earlier acceptance is carried forward to changed bytes. + +Confidence is High for mapping completeness, recorded local assertion failures, exact source identities and canonical planning agreement. This assumes the reviewed tests, imports, conftest closure and mapping stay frozen through native execution. Byte drift requires re-review and new evidence. Baseline rejection of invalid audit inputs can conceal a later overbroad waiver unless full controls run after adoption. Local evidence cannot replace authenticated native RED, so production remains gated on that separate boundary. The former-candidate 3.2.0 probe is owned separately by root and is not an input to this acceptance. No paid or deployment action occurred. Rollback remains the preserved private patches and file copies. Test Behavior, Not Implementation led me to inspect public entry-point outcomes and the observing ordinary clone controls. Prove It Works led me to inspect the real failure messages and rerun the canonical planner instead of accepting summary counts. + +Next action is a durable honest AI acceptance receipt, active tracker reconciliation, normal hooks and the test-only commit, followed by fresh authenticated native RED before production adoption. No new comment/docstring or scoped suppression appears in these four correction test hunks. No comment deletion or encoding is requested by this review. + +The exact reviewed test hashes follow. Paths are relative to the reviewed worktree. + +```json +{ + "tests/unit/security/test_frozen_dependency_artifacts.py": "146f45786ccb74b581c36a1a626130c540631e45fa869433d5663e56202b4215", + "tests/unit/security/test_frozen_gitpython_compatibility.py": "284563d0d316d73910c7857afbc76f083d056f803765d79382c8a54ebfedc162", + "tests/unit/security/test_frozen_jwt_compatibility.py": "35b8e0d571bf50a30599ad1634b068d2d3d37e9a46ffd45fe6dfea03771cab16", + "tests/unit/security/test_release_dependency_selection.py": "f21f4b2ada0bf5f46333088036cbd78742134f0d26b67c397b25db8ec6375600", + "tests/unit/security/test_release_promotion_security_gates.py": "aa04dfa0946822f13a92c9f88b81e22f43033692484cb261afc952d4d2169bae", + "tests/unit/scripts/test_security_audit_gate.py": "f376d745b38404a6e73e67d291c1a26a2620a5104b0b40b8fb8b545ffbaf1b62", + "tests/integration/test_distribution_build.py": "af24d9caa1259ecf9789440c516784ea01d4afb34acf8fe9ef377f5f2f46806b" +} +``` + +The complete current checkpoint inputs and preserved fixture identities follow. + +```json +{ + "openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md": "791c42256b69ad929091a45073b94db5bbf64bc5fb42eeda8188fe4fcff179e2", + "openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md": "92028cde99e5d97d1aab518a4746bcb98250e324e34310d97c63c6c1a26c9ce6", + "openspec/changes/isolate-semgrep-security-runtime/design.md": "e18d70a6c2c866a53c62c352531114e745d66964eb2c0fe940a0eb9d0154fb30", + "openspec/changes/isolate-semgrep-security-runtime/proposal.md": "a2f7551d578a32c17eebb7fa057ac3f4717f8bd05378cf89380e64f4bd94a824", + "openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml": "e56a4eb847ed448707f1118b1f6651c4e0f2c5a8fe254d8f02fae52d009b69f8", + "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md": "715300ab405b73ae080dd6c74c91a3ccd482209812dd23fb8cc0f6806066ca24", + "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-review-pending.md": "cc4eb1521d295737eddb2c63ecd7a15a88baff0d9292a134763cc81c824fb8a4", + "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-advisory-review-evidence.json": "1eb60ab858a5e1afdae98981590482d317a531a29155c27466a9c7a9166d8360", + "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json": "5472841a1154f59f1826f1b5de0ba7374ea432768e4516d2bccefa70f49d9f31", + "openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md": "a17a9b922c078501155950bc0d1e966ce35d3ad49877b92f74214da7fd8f663a", + "openspec/changes/isolate-semgrep-security-runtime/tasks.md": "56b8502a328dfcf1dfb90ea3f05d303cd39336f6af9afba49008e4a6f2ef1cc8", + "tests/integration/scripts/test_runtime_discovery_smoke.py": "65f7afd8b26665b995514587c30c754adc9ffade87f132a167b2e223462c4426", + "tests/unit/docs/test_docs_validation_scripts.py": "571ab47c409ad5941c4dfc556d9aa240c4221d79a18138e0686d5f1fd15594eb", + "tests/unit/scripts/test_check_version_sources.py": "d6fe657b1f201be3bd9193e6326e921046b12412551a25b153ceb1983e8f88a0", + "tests/unit/scripts/test_reproducible_delivery.py": "67d09a6495f9afe619d4570e6bd8df7693bd7be00098145ec68349c1b2c6e515", + "tests/unit/scripts/test_security_audit_gate.py": "f376d745b38404a6e73e67d291c1a26a2620a5104b0b40b8fb8b545ffbaf1b62", + "tests/unit/security/test_frozen_gitpython_compatibility.py": "284563d0d316d73910c7857afbc76f083d056f803765d79382c8a54ebfedc162", + "tests/unit/security/test_release_dependency_selection.py": "f21f4b2ada0bf5f46333088036cbd78742134f0d26b67c397b25db8ec6375600", + "tests/unit/security/test_release_promotion_security_gates.py": "aa04dfa0946822f13a92c9f88b81e22f43033692484cb261afc952d4d2169bae" +} +``` + +Other private review inputs and fresh planner outputs have these hashes. Paths are relative to `/private/tmp/specfact-release-20261022`. + +```json +{ + "advisory-correction-tests/checkpoint.md": "f7b42c250ce4ac0985948aea7267e7417f14c25ee58554ef5c562b8d45b12e64", + "advisory-correction-tests/checkpoint-sha256.json": "2f3c558102c4ead62b5a7f1177d57f023c2696c9e6d29e635743d5704bea76a4", + "advisory-correction-tests/evidence-summary.json": "3f54e5ea342f90df30b5c3d6e2cd67621d4790b0279d5d9eab9f46ce71b63aef", + "advisory-correction-tests/failure-diagnostics.json": "5dda950aec4c8cec18b9241033ebe7b809373603dc2dfa2ed83a120732e897bc", + "advisory-correction-tests/mapped.xml": "e39cc98cc217500edd23697c97b05de9a2306c3acf391fdd845d99585eb9a425", + "advisory-correction-tests/controls.xml": "4b568ffc2679d0712f1d51f878ca09d9b5a6eda2cceddfc7d2275a9bee70616b", + "advisory-correction-tests/plan-final.json": "a57692226f34d8fe58a7766cf2028e68518be47d712814100dcc7b7bf6618f36", + "advisory-correction-tests/planning-final.json": "e8cbd1b7783bfe5df8c36a66ddd16b063653ecd231161976d5a2ef03d146c6bb", + "advisory-correction-tests/selectors.json": "ff55783cbe3601cc3ef4402274b53f2378c7efacefa603773716950e6928e245", + "advisory-correction-tests/mapped-command.json": "759a8c29c9681a62d499af6a8a775568b69966d0c7b41ed5e8d8089007e2fc47", + "advisory-correction-tests/controls-command.json": "71984d843b671b342c43ba1e452975464ced4a28cc5996a4aadc041189878499", + "advisory-correction-tests/mapped-exit.json": "3802820f630ee18199f7e4ac6e77249e0657fb84dbca25ccc9dbb3adc409cc5b", + "advisory-correction-tests/controls-exit.json": "9ffcdb8d15e170f9ad80d6b764195aa76e912d6e79edbf362a928b1ae274fa1e", + "advisory-correction-tests/final-integrity.json": "30bc88be621264e0608ab09b336e9cdadf8d7949c6d145b53cbb7dfdeaece049", + "fixture-fix/checkpoint-sha256.json": "2981488663ed3e41d3a8946db121b2a99cf8bc86da03e7db7425ad7279fdf3d4", + "correction-mapping-review.md": "f411b5a4114a3d7b9233f6852df6df6501ddb4b7c23a52f7e1ccdfaf4b0beddc", + "red-owner/env.sh": "8a2b1fa4222a07b130aa269dd8443c14921c959484ebd67760337e3dfc9d79b7", + "advisory-review-plan.json": "a57692226f34d8fe58a7766cf2028e68518be47d712814100dcc7b7bf6618f36", + "advisory-review-planning.json": "e8cbd1b7783bfe5df8c36a66ddd16b063653ecd231161976d5a2ef03d146c6bb" +} +``` diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-review-pending.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-review-pending.md index a6158c0e..978eea8f 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-review-pending.md +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-review-pending.md @@ -1,4 +1,15 @@ -# Corrected mapping review pending +# Historical corrected mapping review status + +The pending note below is superseded. The 23-case correction received independent +acceptance in correction-mapping-review.md and native RED at +8cce44f449e9559d0100064d629c81991784ab41, run 39843165302 attempt 1. +The accepted receipt is preserved as pre-advisory-review-evidence.json. +Only the new advisory correction remains pending, as recorded in +advisory-correction-review-pending.md. PRRT_kwDOQLaPE86vWprX identified this stale note. + +## Original pending note + +### Corrected mapping review pending The approved correction selects virtualenv 21.14.12, python-discovery 1.6.1, and packaging 26.3. Four new cases bind exact certifi 2026.10.19, GitPython diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-advisory-review-evidence.json b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-advisory-review-evidence.json new file mode 100644 index 00000000..37299e68 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-advisory-review-evidence.json @@ -0,0 +1,9 @@ +{ + "schema_version": "1", + "decision": "accepted", + "reviewer_id": "Codex:/root/release_correction_mapping_review (gpt-6.1-sol)", + "reviewer_role": "independent AI technical mapping reviewer", + "recorded_at": "2026-10-22T19:45:46.737707+00:00", + "reference": "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-mapping-review.md", + "mapping_digest": "sha256:9405ba32fc071ee2092077f516c4d7baf57ac0acde1575025d5545c0cbda04a8" +} diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json index 37299e68..11d0ef0c 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json @@ -1,9 +1,9 @@ { "schema_version": "1", "decision": "accepted", - "reviewer_id": "Codex:/root/release_correction_mapping_review (gpt-6.1-sol)", + "reviewer_id": "Codex:/root/release_advisory_mapping_review (gpt-6.1-sol)", "reviewer_role": "independent AI technical mapping reviewer", - "recorded_at": "2026-10-22T19:45:46.737707+00:00", - "reference": "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/correction-mapping-review.md", - "mapping_digest": "sha256:9405ba32fc071ee2092077f516c4d7baf57ac0acde1575025d5545c0cbda04a8" + "recorded_at": "2026-10-22T22:09:38.527915+00:00", + "reference": "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-mapping-review.md", + "mapping_digest": "sha256:0788c6326b39842e80a8cf4e308ac8508c754c384d9e6479b5cd61f0ca8b1c1d" } diff --git a/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md b/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md index f596f3e3..c2575f14 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md +++ b/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md @@ -36,7 +36,7 @@ The main release SHALL satisfy this delivery contract using the reviewed officia #### Scenario: Select the reviewed compatible tooling graph - **WHEN** the release frozen graph is resolved -- **THEN** it SHALL select certifi 2026.10.19, GitPython 3.2.0, PyJWT 2.15.1, Hatchling 1.32.4, urllib3 2.8.0, virtualenv 21.14.12, python-discovery 1.6.1, and packaging 26.3 +- **THEN** it SHALL select certifi 2026.10.19, GitPython 3.2.1, PyJWT 2.15.1, Hatchling 1.32.4, urllib3 2.8.0, virtualenv 21.14.12, python-discovery 1.6.1, and packaging 26.3 - **AND** the lock and hashed delivery export SHALL agree. ### Requirement: Bound the temporary official MCP advisory exception @@ -76,3 +76,27 @@ The main release SHALL satisfy this delivery contract using the reviewed officia - **WHEN** release metadata is read - **THEN** all four canonical version sources SHALL declare 0.55.11. + +### Requirement: Reject submodule checkouts into Git metadata before cloning + +The main release SHALL use the reviewed official GitPython 3.2.1 artifact to +validate submodule checkout paths before any clone operation. + +#### Scenario: Reject submodule checkouts into Git metadata before cloning + +- **WHEN** the exact frozen GitPython wheel creates a submodule at `.git/hooks`, `GIT~1/hooks`, or the absolute in-checkout `.git/hooks` path +- **THEN** it SHALL raise ValueError before cloning and SHALL leave hooks unchanged with no `.gitmodules` or module metadata created +- **AND** ordinary `vendor/module` and `.github/module` paths SHALL reach the mocked clone boundary exactly once without ValueError. + +### Requirement: Bound the separate OpenTelemetry advisory exception + +The main release SHALL retain a separate exact opentelemetry-api exception for +ordinary local-rule CLI scanning, excluding instrumented HTTP services that +accept untrusted propagation headers. + +#### Scenario: Bound the separate OpenTelemetry advisory exception + +- **WHEN** official Semgrep selects opentelemetry-api 1.37.0 +- **THEN** only CVE-2026-114603 and alias GHSA-xcq7-p3pf-p88h SHALL be waived from 2026-10-22 through 2026-11-04 inclusive +- **AND** other packages or versions, unrelated or mixed advisories, skipped dependencies, missing entries, expired records, and future review dates SHALL fail closed +- **AND** the separate MCP record SHALL preserve its original version, advisory IDs, review date, and expiry. diff --git a/openspec/changes/isolate-semgrep-security-runtime/tasks.md b/openspec/changes/isolate-semgrep-security-runtime/tasks.md index 36a74603..ced3ef43 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/tasks.md +++ b/openspec/changes/isolate-semgrep-security-runtime/tasks.md @@ -1,12 +1,12 @@ # Main release tasks - [x] Record the owner-approved correction to virtualenv 21.14.12, python-discovery 1.6.1, and packaging 26.3. -- [ ] Independently accept the corrected 23-case mapping, including exact certifi, GitPython, PyJWT, and packaging selections. -- [ ] Retain the accepted review with a repository-relative or immutable reference. -- [ ] Commit test-authored content with all local hooks and retain native failing CI evidence. +- [x] Independently accept the corrected 23-case mapping, including exact certifi, GitPython, PyJWT, and packaging selections. +- [x] Retain the accepted 23-case review with a repository-relative reference. +- [x] Commit the 23-case test-authored content with local hooks and retain native failing CI evidence. - [ ] Freeze mappings, selected tests, ancestor conftest files, and transitive Python imports. - [ ] Refresh the nine reviewed official Python packages and both frozen files. -- [ ] Add only the exact approved MCP advisory exception. +- [ ] Add the two separate exact approved MCP and OpenTelemetry advisory exceptions after new native RED. - [ ] Update documentation dependency declarations and lock, then execute real gem compatibility probes. - [ ] After native RED acceptance, port the reviewed dev Ruby compatibility probe to `scripts/check_jekyll_dependencies.rb` and its CI workflow to `.github/workflows/docs-dependency-checks.yml`. - [ ] Add the missing bundle-mapper source-distribution inclusion and synchronize version 0.55.11 with the changelog. @@ -14,3 +14,31 @@ - [ ] Complete independent final review and present the unmerged PR. - [x] Update the internal wiki mirror and rebuild its graph through the coordinating agent. - [ ] Archive with the native OpenSpec command only after the release change is complete. + +## Verification status on 2026-10-22 + +- [x] Apply the approved corrected dependency selections after authenticated native RED. +- [x] Verify all 23 mapped cases and all seven complete regression files locally. +- [x] Build the actual wheel, verify installed-wheel consumers and preserve the frozen source/test boundary. +- [x] Obtain owner approval for GitPython 3.2.1 and the separate exact OpenTelemetry exception. +- [x] Correct four verification fixtures and establish the reviewed full-suite pass without parent environment drift. +- [ ] Obtain native GREEN, actual Linux frozen Ruby CI, normal hooks and independent final review. + +See the appended corrected-production section in TDD_EVIDENCE.md for actual results and limitations. + +## Current advisory correction + +- [x] Verify and back up the twelve production paths and preserve the four reviewed fixture files. +- [x] Restore only the owned production paths before changing the specification and tests. +- [x] Specify Git metadata path rejection before clone and the exact separate OpenTelemetry exception. +- [x] Author and format the corrected mapping, artifact probe, and audit scenarios. +- [x] Run released canonical planning against exact base 5262efb0881e4fac7697d629b0530250342bc656. +- [x] Inspect genuine local failures for every selected case and pass every supplementary control. +- [x] Obtain fresh independent mapping acceptance in requirements-proof/advisory-mapping-review.md. +- [ ] Authenticate new native RED before production adoption. +- [ ] Reapply only the approved production correction and complete release verification. + +The earlier 23-case acceptance and native RED are complete historical evidence. +Their bytes do not approve this newly changed specification or tests. Review +thread PRRT_kwDOQLaPE86vWprX correctly identified the stale status statements; +they are reconciled here and in CHANGE_VALIDATION.md and TDD_EVIDENCE.md. diff --git a/tests/integration/scripts/test_runtime_discovery_smoke.py b/tests/integration/scripts/test_runtime_discovery_smoke.py index 8f6a5a9f..bd3c89aa 100644 --- a/tests/integration/scripts/test_runtime_discovery_smoke.py +++ b/tests/integration/scripts/test_runtime_discovery_smoke.py @@ -4,6 +4,7 @@ import shutil import subprocess import sys +from importlib.metadata import distributions from pathlib import Path import pytest @@ -53,15 +54,43 @@ def _resolve_modules_repo() -> Path | None: @pytest.mark.integration -@pytest.mark.timeout(180) -def test_runtime_discovery_smoke_direct_launcher() -> None: +@pytest.mark.timeout(600) +def test_runtime_discovery_smoke_direct_launcher(tmp_path: Path) -> None: modules_repo = _resolve_modules_repo() if modules_repo is None: pytest.skip("specfact-cli-modules checkout not available") + parent_packages = sorted((dist.metadata["Name"], dist.version) for dist in distributions()) + venv = tmp_path / "runtime-venv" + bin_dir = venv / ("Scripts" if os.name == "nt" else "bin") + python = bin_dir / ("python.exe" if os.name == "nt" else "python") + env = os.environ.copy() + env.update( + VIRTUAL_ENV=str(venv), + HATCH_ENV_TYPE_VIRTUAL_PATH=str(venv), + UV_PROJECT_ENVIRONMENT=str(venv), + PATH=os.pathsep.join([str(bin_dir), env.get("PATH", "")]), + ) + for command in ( + ["uv", "venv", "--python", sys.executable, str(venv)], + ["uv", "pip", "sync", "--python", str(python), "--require-hashes", "requirements/ci/locked.txt"], + ["uv", "pip", "install", "--python", str(python), "--no-deps", "--no-build-isolation", "-e", "."], + ): + bootstrap = subprocess.run( + command, + cwd=REPO_ROOT, + env=env, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + timeout=120, + check=False, + ) + assert bootstrap.returncode == 0, bootstrap.stdout + result = subprocess.run( [ - sys.executable, + str(python), str(REPO_ROOT / "scripts" / "runtime_discovery_smoke.py"), "--modules-repo", str(modules_repo), @@ -69,6 +98,7 @@ def test_runtime_discovery_smoke_direct_launcher() -> None: "direct", ], cwd=REPO_ROOT, + env=env, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -76,6 +106,8 @@ def test_runtime_discovery_smoke_direct_launcher() -> None: check=False, ) + print(result.stdout) + assert sorted((dist.metadata["Name"], dist.version) for dist in distributions()) == parent_packages assert result.returncode == 0, result.stdout assert "runtime-discovery smoke passed for launcher=direct" in result.stdout diff --git a/tests/unit/scripts/test_check_version_sources.py b/tests/unit/scripts/test_check_version_sources.py index 1179092f..cd769dce 100644 --- a/tests/unit/scripts/test_check_version_sources.py +++ b/tests/unit/scripts/test_check_version_sources.py @@ -2,6 +2,7 @@ from __future__ import annotations +import os import subprocess import sys from pathlib import Path @@ -50,9 +51,12 @@ def _commit_all(tmp_path: Path, message: str) -> None: def _run_version_check(script: Path, *args: str) -> subprocess.CompletedProcess[str]: + env = os.environ.copy() + env.pop("GITHUB_BASE_REF", None) return subprocess.run( [sys.executable, str(script), *args], cwd=script.parents[1], + env=env, check=False, capture_output=True, text=True, @@ -207,13 +211,7 @@ def test_check_version_sources_reuses_branch_release_for_dependency_follow_up(tm ) subprocess.run(["git", "add", "setup.py"], cwd=tmp_path, check=True, capture_output=True, text=True) - completed = subprocess.run( - [sys.executable, str(script)], - cwd=str(tmp_path), - check=False, - capture_output=True, - text=True, - ) + completed = _run_version_check(script) assert completed.returncode == 0, completed.stderr diff --git a/tests/unit/scripts/test_reproducible_delivery.py b/tests/unit/scripts/test_reproducible_delivery.py index da59371f..4c89e063 100644 --- a/tests/unit/scripts/test_reproducible_delivery.py +++ b/tests/unit/scripts/test_reproducible_delivery.py @@ -216,8 +216,8 @@ def test_reproducible_delivery_verifier_bounds_uv_commands_and_fails_closed_on_t def test_reproducible_delivery_wheel_build_uses_a_locked_backend() -> None: """The no-isolation wheel proof must use the backend pinned in delivery inputs.""" project = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")) - assert project["build-system"]["requires"] == ["hatchling==1.32.0"] - assert "hatchling==1.32.0" in project["project"]["optional-dependencies"]["dev"] + assert project["build-system"]["requires"] == ["hatchling==1.32.4"] + assert "hatchling==1.32.4" in project["project"]["optional-dependencies"]["dev"] assert "twine>=7.0" in project["project"]["optional-dependencies"]["dev"] assert "core-metadata-version" not in project["tool"]["hatch"]["build"]["targets"]["wheel"] diff --git a/tests/unit/scripts/test_security_audit_gate.py b/tests/unit/scripts/test_security_audit_gate.py index 19c08cdb..3053e0a6 100644 --- a/tests/unit/scripts/test_security_audit_gate.py +++ b/tests/unit/scripts/test_security_audit_gate.py @@ -175,57 +175,122 @@ def test_main_allows_only_a_matching_unexpired_reviewed_exception(gate_mod, tmp_ assert "WAIVED" in capsys.readouterr().out -class McpExceptionScenario(NamedTuple): +class AuditExceptionScenario(NamedTuple): version: str advisory_ids: list[str] skip_reason: str | None today: str expected: int message: str + package: str = "mcp" + omit_entry: bool = False @pytest.mark.parametrize( "scenario", [ pytest.param( - McpExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-10-21", 0, "WAIVED"), id="exact-cve" + AuditExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-10-22", 0, "WAIVED"), id="exact-cve" ), pytest.param( - McpExceptionScenario("1.29.0", ["GHSA-84m7-p3x7-pcfv"], None, "2026-10-21", 0, "WAIVED"), id="exact-ghsa" + AuditExceptionScenario("1.29.0", ["GHSA-84m7-p3x7-pcfv"], None, "2026-10-22", 0, "WAIVED"), id="exact-ghsa" ), pytest.param( - McpExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-11-04", 0, "WAIVED"), id="expiry-day" + AuditExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-11-04", 0, "WAIVED"), id="expiry-day" ), pytest.param( - McpExceptionScenario("1.30.0", ["CVE-2026-59951"], None, "2026-10-21", 1, "FAIL:"), id="other-version" + AuditExceptionScenario("1.30.0", ["CVE-2026-59951"], None, "2026-10-22", 1, "FAIL:"), id="other-version" ), pytest.param( - McpExceptionScenario("1.29.0", ["UNRELATED-1"], None, "2026-10-21", 1, "FAIL:"), id="unrelated-advisory" + AuditExceptionScenario("1.29.0", ["UNRELATED-1"], None, "2026-10-22", 1, "FAIL:"), id="unrelated-advisory" ), pytest.param( - McpExceptionScenario("1.29.0", ["CVE-2026-59951", "UNRELATED-1"], None, "2026-10-21", 1, "FAIL:"), + AuditExceptionScenario("1.29.0", ["CVE-2026-59951", "UNRELATED-1"], None, "2026-10-22", 1, "FAIL:"), id="mixed-advisories", ), pytest.param( - McpExceptionScenario( - "1.29.0", ["CVE-2026-59951"], "could not audit", "2026-10-21", 1, "skipped dependency" + AuditExceptionScenario( + "1.29.0", ["CVE-2026-59951"], "could not audit", "2026-10-22", 1, "skipped dependency" ), id="skipped", ), pytest.param( - McpExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-11-05", 1, "not currently valid"), + AuditExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-11-05", 1, "not currently valid"), id="expired", ), pytest.param( - McpExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-10-20", 1, "not currently valid"), + AuditExceptionScenario("1.29.0", ["CVE-2026-59951"], None, "2026-10-20", 1, "not currently valid"), id="future-review", ), + pytest.param( + AuditExceptionScenario("1.37.0", ["CVE-2026-114603"], None, "2026-10-22", 0, "WAIVED", "opentelemetry-api"), + id="otel-exact-cve", + ), + pytest.param( + AuditExceptionScenario( + "1.37.0", ["GHSA-xcq7-p3pf-p88h"], None, "2026-10-22", 0, "WAIVED", "opentelemetry-api" + ), + id="otel-exact-ghsa", + ), + pytest.param( + AuditExceptionScenario("1.37.0", ["CVE-2026-114603"], None, "2026-11-04", 0, "WAIVED", "opentelemetry-api"), + id="otel-expiry-day", + ), + pytest.param( + AuditExceptionScenario("1.38.0", ["CVE-2026-114603"], None, "2026-10-22", 1, "FAIL:", "opentelemetry-api"), + id="otel-other-version", + ), + pytest.param( + AuditExceptionScenario("1.37.0", ["CVE-2026-114603"], None, "2026-10-22", 1, "FAIL:", "other-package"), + id="otel-other-package", + ), + pytest.param( + AuditExceptionScenario("1.37.0", ["UNRELATED-1"], None, "2026-10-22", 1, "FAIL:", "opentelemetry-api"), + id="otel-unrelated-advisory", + ), + pytest.param( + AuditExceptionScenario( + "1.37.0", ["CVE-2026-114603", "UNRELATED-1"], None, "2026-10-22", 1, "FAIL:", "opentelemetry-api" + ), + id="otel-mixed-advisories", + ), + pytest.param( + AuditExceptionScenario( + "1.37.0", + ["CVE-2026-114603"], + "could not audit", + "2026-10-22", + 1, + "skipped dependency", + "opentelemetry-api", + ), + id="otel-skipped", + ), + pytest.param( + AuditExceptionScenario( + "1.37.0", ["CVE-2026-114603"], None, "2026-11-05", 1, "not currently valid", "opentelemetry-api" + ), + id="otel-expired", + ), + pytest.param( + AuditExceptionScenario( + "1.37.0", ["CVE-2026-114603"], None, "2026-10-21", 1, "not currently valid", "opentelemetry-api" + ), + id="otel-future-review", + ), + pytest.param( + AuditExceptionScenario( + "1.37.0", ["CVE-2026-114603"], None, "2026-10-22", 1, "FAIL:", "opentelemetry-api", True + ), + id="otel-missing-entry", + ), ], ) -def test_committed_mcp_exception_is_exact_and_time_bounded( +def test_committed_advisory_exception_is_exact_and_time_bounded( gate_mod: ModuleType, capsys: pytest.CaptureFixture[str], - scenario: McpExceptionScenario, + scenario: AuditExceptionScenario, + tmp_path: Path, ) -> None: class AuditDate(date): @classmethod @@ -233,7 +298,7 @@ def today(cls) -> date: return date.fromisoformat(scenario.today) dependency: dict[str, object] = { - "name": "mcp", + "name": scenario.package, "version": scenario.version, "vulns": [{"id": value} for value in scenario.advisory_ids], } @@ -241,6 +306,11 @@ def today(cls) -> date: dependency["skip_reason"] = scenario.skip_reason proc = MagicMock(stdout=json.dumps({"dependencies": [dependency]}), stderr="", returncode=1) exceptions = Path(__file__).resolve().parents[3] / "ci/vulnerability-audit-exceptions.json" + if scenario.omit_entry: + register = json.loads(exceptions.read_text(encoding="utf-8")) + register["exceptions"] = [entry for entry in register["exceptions"] if entry["package"] != scenario.package] + exceptions = tmp_path / "exceptions.json" + exceptions.write_text(json.dumps(register), encoding="utf-8") with patch.object(gate_mod, "date", AuditDate), patch.object(gate_mod.subprocess, "run", return_value=proc): assert gate_mod.main(["--exceptions", str(exceptions)]) == scenario.expected captured = capsys.readouterr() diff --git a/tests/unit/security/test_frozen_gitpython_compatibility.py b/tests/unit/security/test_frozen_gitpython_compatibility.py index 640ec911..2d7d85eb 100644 --- a/tests/unit/security/test_frozen_gitpython_compatibility.py +++ b/tests/unit/security/test_frozen_gitpython_compatibility.py @@ -5,6 +5,8 @@ import sys from pathlib import Path +import pytest + REPO_ROOT = Path(__file__).resolve().parents[3] FROZEN_WHEEL_INSTALL_FLAGS = ( @@ -86,3 +88,84 @@ def test_submodule_update_rejects_outside_checkout_before_clone(tmp_path: Path) assert result["rejected"] is True assert result["clone_calls"] == 0 assert result["outside_exists"] is False + + +SUBMODULE_ADD_PROBE = """ +import json, sys +from pathlib import Path +from unittest.mock import patch +sys.path.append(sys.argv[1]) +import git +from git.objects.submodule.base import Submodule +workspace = Path(sys.argv[2]) +checkout_path = sys.argv[3] +parent = git.Repo.init(workspace / "parent") +if checkout_path == "absolute-metadata": + checkout_path = str(Path(parent.working_tree_dir) / ".git" / "hooks") +hooks = Path(parent.git_dir) / "hooks" +before_hooks = {str(path.relative_to(hooks)): path.read_bytes().hex() + for path in hooks.rglob("*") if path.is_file()} +class CloneBoundaryReached(Exception): + pass +rejected = False +boundary_reached = False +with patch.object(Submodule, "_clone_repo", side_effect=CloneBoundaryReached) as clone: + try: + parent.create_submodule(name="module", path=checkout_path, url="unused") + except ValueError: + rejected = True + except CloneBoundaryReached: + boundary_reached = True + clone_calls = clone.call_count +after_hooks = {str(path.relative_to(hooks)): path.read_bytes().hex() + for path in hooks.rglob("*") if path.is_file()} +print(json.dumps({"version": git.__version__, "rejected": rejected, + "boundary_reached": boundary_reached, "clone_calls": clone_calls, + "hooks_unchanged": before_hooks == after_hooks, + "gitmodules_exists": (Path(parent.working_tree_dir) / ".gitmodules").exists(), + "module_metadata_exists": (Path(parent.git_dir) / "modules").exists()})) +""" + + +@pytest.mark.parametrize( + ("checkout_path", "expected_rejection"), + [ + pytest.param(".git/hooks", True, id="git-metadata"), + pytest.param("GIT~1/hooks", True, id="git-short-name"), + pytest.param("absolute-metadata", True, id="absolute-metadata"), + pytest.param("vendor/module", False, id="ordinary-checkout"), + pytest.param(".github/module", False, id="github-checkout"), + ], +) +def test_submodule_creation_validates_checkout_before_clone( + tmp_path: Path, checkout_path: str, expected_rejection: bool +) -> None: + requirement, version = _frozen_gitpython_requirement(tmp_path) + uv = shutil.which("uv") + assert uv is not None, "The frozen delivery probe requires uv" + target = tmp_path / "delivery-package" + subprocess.run( + [uv, *FROZEN_WHEEL_INSTALL_FLAGS, "--target", str(target), "-r", str(requirement)], + check=True, + capture_output=True, + text=True, + timeout=120, + ) + observed = subprocess.run( + [sys.executable, "-I", "-S", "-c", SUBMODULE_ADD_PROBE, str(target), str(tmp_path), checkout_path], + check=False, + capture_output=True, + text=True, + timeout=10, + ) + assert observed.returncode == 0, observed.stderr + result = json.loads(observed.stdout) + assert result == { + "version": version, + "rejected": expected_rejection, + "boundary_reached": not expected_rejection, + "clone_calls": 0 if expected_rejection else 1, + "hooks_unchanged": True, + "gitmodules_exists": False, + "module_metadata_exists": False, + } diff --git a/tests/unit/security/test_release_dependency_selection.py b/tests/unit/security/test_release_dependency_selection.py index 9946dd12..014c3351 100644 --- a/tests/unit/security/test_release_dependency_selection.py +++ b/tests/unit/security/test_release_dependency_selection.py @@ -14,7 +14,7 @@ ("name", "version"), [ ("certifi", "2026.10.19"), - ("gitpython", "3.2.0"), + ("gitpython", "3.2.1"), ("pyjwt", "2.15.1"), ("hatchling", "1.32.4"), ("urllib3", "2.8.0"), diff --git a/tests/unit/security/test_release_promotion_security_gates.py b/tests/unit/security/test_release_promotion_security_gates.py index cf69989e..e29c87a8 100644 --- a/tests/unit/security/test_release_promotion_security_gates.py +++ b/tests/unit/security/test_release_promotion_security_gates.py @@ -398,31 +398,44 @@ def test_frozen_graph_uses_official_semgrep_with_bounded_mcp_exception() -> None locked = (REPO_ROOT / "requirements" / "ci" / "locked.txt").read_text(encoding="utf-8") assert set(re.findall(r"^(?:semgrep|mcp)==[^\s]+", locked, re.MULTILINE)) == {"semgrep==1.179.0", "mcp==1.29.0"} + assert re.search(r"^opentelemetry-api==1\.37\.0\s", locked, re.MULTILINE) policy = json.loads((REPO_ROOT / "ci" / "security-tool-minimum-versions.json").read_text(encoding="utf-8")) assert policy["minimum_versions"] == {"mcp": "1.28.1", "semgrep": "1.175.0"} exceptions = json.loads((REPO_ROOT / "ci" / "vulnerability-audit-exceptions.json").read_text(encoding="utf-8")) records = exceptions["exceptions"] - assert len(records) == 1, "Only the reviewed MCP advisory may be waived" - record = records[0] - expected_record = { - "package": "mcp", - "version": "1.29.0", - "vulnerability_ids": ["CVE-2026-59951", "GHSA-84m7-p3x7-pcfv"], - "reviewed_on": "2026-10-21", - "expires_on": "2026-11-04", - } - actual_record = {key: record[key] for key in expected_record} - actual_record["vulnerability_ids"] = sorted(record["vulnerability_ids"]) - assert actual_record == expected_record - assert tuple(bool(record[key].strip()) for key in ("rationale", "mitigation")) == (True, True) + assert len(records) == 2, "Only the separate reviewed MCP and OpenTelemetry advisories may be waived" + expected_records = [ + { + "package": "mcp", + "version": "1.29.0", + "vulnerability_ids": ["CVE-2026-59951", "GHSA-84m7-p3x7-pcfv"], + "reviewed_on": "2026-10-21", + "expires_on": "2026-11-04", + }, + { + "package": "opentelemetry-api", + "version": "1.37.0", + "vulnerability_ids": ["CVE-2026-114603", "GHSA-xcq7-p3pf-p88h"], + "reviewed_on": "2026-10-22", + "expires_on": "2026-11-04", + }, + ] + for record, expected_record in zip( + sorted(records, key=lambda entry: entry["package"]), expected_records, strict=True + ): + actual_record = {key: record[key] for key in expected_record} + actual_record["vulnerability_ids"] = sorted(record["vulnerability_ids"]) + assert actual_record == expected_record + assert tuple(bool(record[key].strip()) for key in ("rationale", "mitigation")) == (True, True) lock = tomllib.loads((REPO_ROOT / "uv.lock").read_text(encoding="utf-8")) packages = [ (package["name"], package["version"], package["source"]) for package in lock["package"] - if package["name"] in {"semgrep", "mcp", "specfact-semgrep"} + if package["name"] in {"semgrep", "mcp", "opentelemetry-api", "specfact-semgrep"} ] assert sorted(packages) == [ ("mcp", "1.29.0", {"registry": "https://pypi.org/simple"}), + ("opentelemetry-api", "1.37.0", {"registry": "https://pypi.org/simple"}), ("semgrep", "1.179.0", {"registry": "https://pypi.org/simple"}), ] From 063cc61f725f4142cfe10433be7909a9d8d642a0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:54:09 +0000 Subject: [PATCH 6/7] test(security): reconcile release proof quality Stage: prefix7 Source: 4936750186fcb339913a8710f3034cf1d2be901f Ordinal: 6 Manifest: 6c3d344ecb69beca59231c3c49ae05dba50790fd253db87f37122028de1dfa2a Executor: bd639eb85d78f5a3d3438893297b31f5b3a93e88 Run: 39949294200 Attempt: 1 --- .../CHANGE_VALIDATION.md | 10 +- .../TDD_EVIDENCE.md | 56 +++++++- .../advisory-correction-review-pending.md | 4 +- .../pre-quality-review-evidence.json | 9 ++ .../quality-mapping-review.md | 101 ++++++++++++++ .../requirements-proof/review-evidence.json | 6 +- .../isolate-semgrep-security-runtime/tasks.md | 13 ++ .../scripts/test_runtime_discovery_smoke.py | 1 - .../unit/docs/test_docs_validation_scripts.py | 124 +++++++----------- .../unit/scripts/test_security_audit_gate.py | 36 ++--- .../test_release_promotion_security_gates.py | 11 +- 11 files changed, 264 insertions(+), 107 deletions(-) create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-quality-review-evidence.json create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/quality-mapping-review.md diff --git a/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md b/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md index d7d97309..d5193b96 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md +++ b/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md @@ -17,5 +17,13 @@ The owner separately approved GitPython 3.2.1 and the exact opentelemetry-api 1.37.0 exception on 2026-10-22. Fresh independent mapping acceptance for this new correction is recorded in requirements-proof/advisory-mapping-review.md. Only new native RED remains pending. All twelve saved production paths were verified and backed up before restoration to HEAD. The four independently -reviewed fixture corrections are retained unchanged. The coordinating agent owns +reviewed fixture corrections were preserved, then received the bounded test-only +quality amendment recorded in requirements-proof/quality-mapping-review.md. The coordinating agent owns the matching internal wiki update and has reported its graph rebuild complete. + +On 2026-10-23 the coordinator refreshed issue metadata and verified OPEN/Todo, +labels and project assignment. Fresh independent AI technical acceptance covers +the quality amendment, unchanged canonical 31-case plan, genuine local RED and +23 passing controls. The active receipt references quality-mapping-review.md. +The docs floor hunk is excluded from the test-only commit and held for matching +production metadata. Fresh native RED remains required before adoption. diff --git a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md index 638499fb..9d0a0eec 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md +++ b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md @@ -7,11 +7,16 @@ RED at commit 8cce44f449e9559d0100064d629c81991784ab41, run 39843165302 attempt Its accepted receipt is preserved in `requirements-proof/pre-advisory-review-evidence.json`. The subsequent local production results below remain authentic history. -The owner has now approved GitPython 3.2.1 and the separate exact OpenTelemetry -exception. Production inputs have been restored before specification and test -changes. Fresh independent acceptance for the 31-case advisory correction is recorded in -`requirements-proof/advisory-mapping-review.md` on 2026-10-23. Only new native RED -remains pending. The active receipt uses that repository-relative accepted report. No earlier proof is reused to approve changed test or mapping bytes. +The owner approved GitPython 3.2.1 and the separate exact OpenTelemetry exception. +Production inputs were restored before the specification and test changes. +The 31-case advisory mapping first received independent acceptance in +`requirements-proof/advisory-mapping-review.md`. A subsequent bounded test-only +quality amendment received fresh independent AI technical acceptance in +`requirements-proof/quality-mapping-review.md` on 2026-10-23. The active receipt +uses that repository-relative report. Both earlier receipts remain historical. +Fresh native RED for the amended test bytes remains pending. No earlier native +proof approves these changed bytes. The docs floor fixture remains held for its +matching production metadata, separate from the accepted test-helper repair. ## Superseded 23-case pending note @@ -439,3 +444,44 @@ hook because the updated JSON assertion expected its matching Gemfile change. That one reviewed fixture is retained locally for the production commit with the Gemfile. The 31 mapped tests, selectors and accepted proof bytes are unchanged. No hook exception, skip or production adoption was used. + +## Quality amendment and refreshed local RED on 2026-10-23 + +The complete PR Code Review initially reported 67 warnings and six informational +inspection recommendations. The bounded repair adds truthful module/capture types, +removes redundant smoke output, gives the malformed-byte and guidance cases precise +names, consolidates the four unchanged Markdown examples, and states the five-field +exception projection explicitly. Actual Radon complexity changes from 13 to 12. +No policy, assertion, tool configuration, suppression or production file changes. + +The final actual review reports zero errors, zero warnings and six visible infos. +Its unmodified JSON SHA-256 is +`d932e26b4795aab317f9c03fb91ae536e1fc95cf6d3806c6c2f01f54c9592d5a`. +The independent review checks each retained informational recommendation against +source and records the specific provenance, policy literals, formats or mandatory +argv that justify its expanded test. These are completed inspection dispositions, +not hidden findings. A fresh comment review finds no changed comments or +suppressions and no actionable flags. No comment deletion or restoration occurs. + +Root ran the exact 31 mapped selectors after the quality edits against all twelve +restored production inputs. Result is 31 failures, zero passes, errors or skips. +All 23 supplementary controls pass. Failure messages and tracebacks were inspected. +They reproduce the intended old dependency behavior, missing records, source-build +inclusion and exact selection/version mismatches. The canonical plan is unchanged. +The independent reviewer reran that planner, compared the complete plan and source +hashes, verified the XML and every assertion, and accepted this bounded amendment. +This AI technical review supplies no human authority or release approval. + +The exact commands, exits, timestamps and XML remain in +`/private/tmp/specfact-release-20261022/quality-red/`. Mapped XML SHA-256 is +`7987303f817d4f24e6221873539f66d6c6f4d31a8cb8c9b954566c3008dec155`. +Controls XML SHA-256 is +`ebbab7bf849a76a60a57cc879ae006f5f5567500253e8b13d084d626aa88a777`. +The repository-relative accepted report binds all seven mapped tests, four quality +files, proposal/spec/mapping and actual evidence hashes. The source mapping digest +remains `sha256:0788c6326b39842e80a8cf4e308ac8508c754c384d9e6479b5cd61f0ca8b1c1d`. + +Normal partial staging retains the old docs-floor assertion in this test-only +commit. Its already-reviewed new floor stays unstaged for the matching Gemfile +adoption. The four Markdown examples, five memberships and fresh loader behavior +are staged together. Native RED is still required before production adoption. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md index 4c448069..accf0225 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md @@ -1,8 +1,8 @@ # Historical advisory correction pending note The pending note below is superseded by accepted independent AI technical -review in `advisory-mapping-review.md` on 2026-10-23. The active receipt records -that acceptance. Only new authenticated native RED remains pending before +review in `advisory-mapping-review.md` on 2026-10-23. That acceptance is retained as history. The active receipt now records the +subsequent quality amendment acceptance in quality-mapping-review.md. Only new authenticated native RED remains pending before production adoption. This does not grant human, merge or publication authority. ## Original pending note diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-quality-review-evidence.json b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-quality-review-evidence.json new file mode 100644 index 00000000..11d0ef0c --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-quality-review-evidence.json @@ -0,0 +1,9 @@ +{ + "schema_version": "1", + "decision": "accepted", + "reviewer_id": "Codex:/root/release_advisory_mapping_review (gpt-6.1-sol)", + "reviewer_role": "independent AI technical mapping reviewer", + "recorded_at": "2026-10-22T22:09:38.527915+00:00", + "reference": "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-mapping-review.md", + "mapping_digest": "sha256:0788c6326b39842e80a8cf4e308ac8508c754c384d9e6479b5cd61f0ca8b1c1d" +} diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/quality-mapping-review.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/quality-mapping-review.md new file mode 100644 index 00000000..dd4aa353 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/quality-mapping-review.md @@ -0,0 +1,101 @@ +TL;DR. Accepted for the bounded test-only quality amendment, excluding the inherited docs security-floor hunk. No blocking technical finding remains in this reviewed scope. This is an independent AI technical acceptance, never human approval, release approval, merge authority, or permission to adopt production dependencies. + +Reviewed 2026-10-23T00:38:34.820675+02:00. HEAD is `fe1e1e9c0ce77ee0b3b620a882c77b0be8501860`. The selected change is `isolate-semgrep-security-runtime`. I read the actual proposal, isolated-scanner-runtime spec, 31-case YAML mapping, review receipt, seven mapped Python sources, four-file working-tree delta, quality checkpoint and unedited machine report. Poteto mode and repository bootstrap, OpenSpec/TDD and quality rules were read. Prove It Works led me to compare actual source and XML, independently rerun canonical planning, and retain a repeatable private verifier. Unslop shaped the report wording. The parent assignment forbids delegation and repository writes, so no agents or repository edits were made. + +The active plan is unchanged from the prior advisory correction. My canonical planner output equals both `quality-red/plan.json` and `advisory-correction-tests/accepted-plan.json` as complete parsed JSON. All three retain exactly 31 unique selectors. The current authority-test rename is outside this selected 31-case mapping. The review receipt still binds the same source mapping digest. No mapping refresh is necessary for this bounded amendment. + +Canonical digests, distinct from raw file SHA-256 values: + +- Combined mapping digest `sha256:d4d5c52f4194122dd0745e808dd29d9e19104cc704256c5c66414c3d247d0dbd`. +- Selected source mapping digest `sha256:0788c6326b39842e80a8cf4e308ac8508c754c384d9e6479b5cd61f0ca8b1c1d`. +- Plan identity digest `sha256:59785c8bb37070043e729a0d06c63ce1841f39c4631038fa010e9fc7d8437a69`. +- Plan digest `sha256:597f3228ceb13ee190bed760bae90988af631d4f0714105077f446291cfdd438`. + +The audit-test AST is identical after removing annotations. The smoke-test AST is identical after removing only the redundant print. Parent package inventory equality, subprocess return-code diagnostics and smoke success marker all remain. The release-test AST is identical after normalizing the authority-test rename and replacing the old dynamic projection with the same five explicit fields. Both expected records contain exactly package, version, vulnerability_ids, reviewed_on and expires_on. Advisory IDs remain sorted. Full record equality, two-record cardinality, both rationale/mitigation checks, origins, frozen versions and scanner-policy assertions remain. + +The docs refactor preserves four exact input strings, including all whitespace, and all five membership expectations. The parameter IDs are code-block, chained-and, flags and leading-global-options. Membership remains membership. Every other docs test is AST-identical after normalizing loader calls and the guidance-test rename. I independently executed the new loader twice for each of check-docs-commands, check-command-contract and check-cross-site-links. Each returned a fresh module, did not register its name in sys.modules, and did not leak an injected mutation. The floor test is byte-identical to the initial working-tree backup. Its inherited floor change must remain held for production. + +The mapped XML contains 31 tests, 31 failures, zero errors and zero skips. The controls XML contains 23 tests, zero failures, zero errors and zero skips. Commands, recorded exits and XML node identities agree exactly. The 31 XML nodes equal the canonical plan in order. The 23 controls are unique and disjoint. The 31-entry failure diagnostics agree with the meaningful assertion failures inspected in the XML. I did not rerun either lane or a broad suite. + +The mapped failures are intended behavior mismatches. The six exact/inclusive-date waiver cases reject missing approved records; four invalid-date cases fail on the missing stable date diagnostic. The frozen graph still selects Semgrep 1.175.0, rather than 1.179.0. Certifi retains the specified compromised root. PyJWT inspection mutates the caller options. Release metadata still declares 0.55.4. Eight exact Python version rows, three Ruby plugin rows and the JSON floor differ from the approved selections. These are assertion failures after test setup, rather than collection or dependency-install failures. + +The source-to-wheel test reaches the real backend and fails because the extracted sdist lacks modules/bundle-mapper, matching the source-distribution requirement. The outside Git update reaches the patched _clone_repo sentinel and raises "clone boundary reached"; its nonzero subprocess status is the intended pre-clone containment failure. The three metadata creation cases return the wrong rejection/boundary results after successful probe execution. All clone operations are mocked before the unsafe action. The ordinary vendor/module and .github/module controls each reach the clone sentinel exactly once, with unchanged hooks and no .gitmodules or module metadata. No unsafe clone was attempted. + +The actual Code Review JSON has SHA-256 d932e26b4795aab317f9c03fb91ae536e1fc95cf6d3806c6c2f01f54c9592d5a. Its actual verdict is PASS_WITH_ADVISORY, with zero errors, zero warnings and six informational design judgments. I inspected each source independently and accept the following retain dispositions. This report does not turn those machine findings into absent findings. + +- The 54-line runtime smoke test expresses the disposable environment, three bootstrap commands, direct launcher, separate timeouts and parent inventory invariant. The removed print was redundant. Further extraction would separate the isolation contract from its assertions. +- The 45-line known-navigation test exposes two Markdown permalink formats and the real navigation YAML. Literal fixtures are appropriate evidence for author-facing formats. There is no redundant loop or branch to collapse. +- The 48-line PyJWT test retains the exact hash-pinned install flags and isolated probe, followed by version, subject, caller options, expiry, signed claims and padded-token assertions. A shared installer would hide delivery provenance. +- The 44-line fresh-consumer test enumerates required trusted components, forbidden shortcuts, import ordering, execution ordering and timeout. Its literals identify separate trust conditions. Retaining them makes failures inspectable. +- The 53-line graph test now states the exact five-field exception contract explicitly. Both records and all origins, scanner choices and explanatory fields remain. Further collapsing policy literals would make the contract harder to review. +- The 43-line malformed-byte authority test supplies all mandatory CLI flags, invokes the actual boundary, and checks exit one with the exact sanitized diagnostic. Most length is argv. The precise rename preserves the assertion body. + +Only the assigned four test files differ from HEAD and the index is unchanged. I independently rehashed all 2410 other tracked files against the quality worker's initial inventory; every hash matches. Current production, locks, manifests, policy, scripts, mapping and receipt therefore remain at HEAD. The owner RED runner invokes only its recorded pytest commands; tests install locked artifacts into disposable --target paths and the source build uses --offline --no-build-isolation. Neither this review nor the shown runner synchronizes the shared environment. I did not run installation or synchronization commands. This is evidence about the inspected commands and present state, not an audit of unrecorded historical shell activity. + +Commands and observed results: + +```sh +cd /private/tmp/specfact-release-20261022/worktrees/hotfix/security-dependencies-0-55-11 +. /private/tmp/specfact-release-20261022/red-owner/env.sh +export GITHUB_BASE_REF=main +python scripts/requirements_evidence_delivery_gate.py --base-ref 5262efb0881e4fac7697d629b0530250342bc656 --required-maturity planned --output /private/tmp/specfact-release-20261022/quality-red/reviewer/planning.json --summary /private/tmp/specfact-release-20261022/quality-red/reviewer/planning.md --plan-output /private/tmp/specfact-release-20261022/quality-red/reviewer/plan.json +# Exit 0. Planned maturity, gate pass, 31 cases. No implementation-evidence claim. +python -B /private/tmp/specfact-release-20261022/quality-red/reviewer/verify_acceptance.py +# Exit 0. See verification.json and verification.log for all source/AST/XML/hash comparisons. +git diff --check +# Exit 0. +git status --short +# Exactly the four assigned modified test paths. No staged paths. +``` + +The root-owned RED commands below were read from the actual command and exit JSON. They were not rerun by this reviewer. + +```sh +/private/tmp/specfact-release-20261022/worktrees/hotfix/security-dependencies-0-55-11/.venv/bin/python -m pytest --override-ini addopts= -q -rA --junitxml=/private/tmp/specfact-release-20261022/quality-red/mapped.xml 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-exact-cve]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-exact-ghsa]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-expired]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-expiry-day]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-future-review]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[exact-cve]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[exact-ghsa]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[expired]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[expiry-day]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[future-review]' tests/unit/security/test_release_promotion_security_gates.py::test_frozen_graph_uses_official_semgrep_with_bounded_mcp_exception tests/unit/security/test_frozen_dependency_artifacts.py::test_frozen_certifi_bundle_excludes_compromised_root tests/unit/security/test_frozen_jwt_compatibility.py::test_inspection_preserves_options_and_expiry_on_reuse tests/unit/security/test_release_promotion_security_gates.py::test_patch_release_uses_next_version_in_all_sources tests/integration/test_distribution_build.py::test_source_distribution_rebuild_preserves_bundle_mapper 'tests/unit/security/test_frozen_gitpython_compatibility.py::test_submodule_creation_validates_checkout_before_clone[absolute-metadata]' 'tests/unit/security/test_frozen_gitpython_compatibility.py::test_submodule_creation_validates_checkout_before_clone[git-metadata]' 'tests/unit/security/test_frozen_gitpython_compatibility.py::test_submodule_creation_validates_checkout_before_clone[git-short-name]' tests/unit/security/test_frozen_gitpython_compatibility.py::test_submodule_update_rejects_outside_checkout_before_clone 'tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[certifi-2026.10.19]' 'tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[gitpython-3.2.1]' 'tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[hatchling-1.32.4]' 'tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[packaging-26.3]' 'tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[pyjwt-2.15.1]' 'tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[python-discovery-1.6.1]' 'tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[urllib3-2.8.0]' 'tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[virtualenv-21.14.12]' 'tests/unit/security/test_release_dependency_selection.py::test_reviewed_documentation_plugins_are_locked[jekyll-feed-0.18.0]' 'tests/unit/security/test_release_dependency_selection.py::test_reviewed_documentation_plugins_are_locked[jekyll-redirect-from-0.17.0]' 'tests/unit/security/test_release_dependency_selection.py::test_reviewed_documentation_plugins_are_locked[jekyll-relative-links-0.9.1]' tests/unit/security/test_release_dependency_selection.py::test_documentation_json_has_reviewed_floor_and_major_bound +``` + +Recorded exit 1; start 2026-10-22T22:34:25.621665+00:00; finish 2026-10-22T22:34:27.612292+00:00. + +```sh +/private/tmp/specfact-release-20261022/worktrees/hotfix/security-dependencies-0-55-11/.venv/bin/python -m pytest --override-ini addopts= -q -rA --junitxml=/private/tmp/specfact-release-20261022/quality-red/controls.xml tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_wrong_sha256 'tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[duplicate_member]' 'tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[duplicate_record]' 'tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[traversal]' 'tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[extra_member]' 'tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[corrupt_payload]' 'tests/unit/security/test_frozen_dependency_artifacts.py::test_wheel_reader_rejects_malformed_artifacts[wrong_identity]' 'tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[http]' 'tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[other-host]' 'tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[credentials]' 'tests/unit/security/test_frozen_dependency_artifacts.py::test_artifact_download_rejects_unapproved_urls[query]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[other-version]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[unrelated-advisory]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[mixed-advisories]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[skipped]' 'tests/unit/security/test_frozen_gitpython_compatibility.py::test_submodule_creation_validates_checkout_before_clone[ordinary-checkout]' 'tests/unit/security/test_frozen_gitpython_compatibility.py::test_submodule_creation_validates_checkout_before_clone[github-checkout]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-other-version]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-other-package]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-unrelated-advisory]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-mixed-advisories]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-skipped]' 'tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-missing-entry]' +``` + +Recorded exit 0; start 2026-10-22T22:34:27.612694+00:00; finish 2026-10-22T22:34:28.259683+00:00. + +Exact raw SHA-256 bindings. Test hashes cover all seven mapped sources and all four quality files, with two overlaps. Private artifact paths are relative to `/private/tmp/specfact-release-20261022`; repository paths are relative to the reviewed worktree. + +| File | SHA-256 | +| --- | --- | +| `tests/integration/scripts/test_runtime_discovery_smoke.py` | `7f5241ac4299e4638b36bca06c6f81fb4d46575af65af00bc4fc133ac4845c2d` | +| `tests/integration/test_distribution_build.py` | `af24d9caa1259ecf9789440c516784ea01d4afb34acf8fe9ef377f5f2f46806b` | +| `tests/unit/docs/test_docs_validation_scripts.py` | `29aae452c99fa57f49df3758951f294a8cdfbfb22c5df1b77631a8ea003a9c98` | +| `tests/unit/scripts/test_security_audit_gate.py` | `5a0c6669c793477bc8cabe518a7f7d20e8db98bb2201be255ed241654c4ed748` | +| `tests/unit/security/test_frozen_dependency_artifacts.py` | `146f45786ccb74b581c36a1a626130c540631e45fa869433d5663e56202b4215` | +| `tests/unit/security/test_frozen_gitpython_compatibility.py` | `284563d0d316d73910c7857afbc76f083d056f803765d79382c8a54ebfedc162` | +| `tests/unit/security/test_frozen_jwt_compatibility.py` | `35b8e0d571bf50a30599ad1634b068d2d3d37e9a46ffd45fe6dfea03771cab16` | +| `tests/unit/security/test_release_dependency_selection.py` | `f21f4b2ada0bf5f46333088036cbd78742134f0d26b67c397b25db8ec6375600` | +| `tests/unit/security/test_release_promotion_security_gates.py` | `9f0ede9d55afa5241b42b30660bec58099945a9b636b1a7c57cda69139a10487` | +| `openspec/changes/isolate-semgrep-security-runtime/proposal.md` | `a2f7551d578a32c17eebb7fa057ac3f4717f8bd05378cf89380e64f4bd94a824` | +| `openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md` | `a17a9b922c078501155950bc0d1e966ce35d3ad49877b92f74214da7fd8f663a` | +| `openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml` | `e56a4eb847ed448707f1118b1f6651c4e0f2c5a8fe254d8f02fae52d009b69f8` | +| `openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json` | `2bc3703a7867301e595ce0aff9840df48b10bfd842f39a04955b60d83ca300d7` | +| `quality-red/mapped.xml` | `7987303f817d4f24e6221873539f66d6c6f4d31a8cb8c9b954566c3008dec155` | +| `quality-red/controls.xml` | `ebbab7bf849a76a60a57cc879ae006f5f5567500253e8b13d084d626aa88a777` | +| `quality-red/source-hashes.json` | `909b3af10e6af6add3365046dd6762b2dfa6a62d9a706456c1bfbf5ca9266c90` | +| `quality-red/failure-diagnostics.json` | `a194856bddd285bf2049af96c7807a6e1cc9c47a6a6a53ce6325e4542fb19f07` | +| `quality-red/mapped-command.json` | `abf82bbdd7f4b530742e7b1f28e55e55544d1107cd87d58ce6dc922903a74c9d` | +| `quality-red/mapped-exit.json` | `56684af4e0d2a41fbe4aedc3f2ac286d1cd4c3bfcf97008323258a9fe6190394` | +| `quality-red/controls-command.json` | `db17c343f84683dbe5eedcaa9a6f85ab8300661b6fe45d543d5ccdc085440c77` | +| `quality-red/controls-exit.json` | `67279a019ebf9bd71e143b6cdaf1e8cabe8ac1e6927d02c704c1aa0450547ec6` | +| `quality-red/plan.json` | `a57692226f34d8fe58a7766cf2028e68518be47d712814100dcc7b7bf6618f36` | +| `quality-red/reviewer/plan.json` | `a57692226f34d8fe58a7766cf2028e68518be47d712814100dcc7b7bf6618f36` | +| `quality-repair/all-pr-code-review.json` | `d932e26b4795aab317f9c03fb91ae536e1fc95cf6d3806c6c2f01f54c9592d5a` | +| `quality-repair/quality-only.patch` | `d5f8dc3b8c260d9481cd7e2c342f22a79a8751b4054828c17d0f18759be704af` | + +Confidence is high for this bounded technical acceptance because the exact source, unchanged plan, substantive RED assertions, passing controls and six retain dispositions were independently checked. Release readiness remains outside this verdict. No fresh advisory research or production compatibility claim was made. The approved version and advisory facts were treated as the supplied acceptance contract. + +Assumptions that could change the verdict are subsequent changes to the bound source/evidence, or staging the inherited docs-floor hunk as part of the test-only amendment. A stale native proof could otherwise fail to represent these exact tests. Mitigate those risks by rechecking hashes before recording the receipt, staging only quality-only.patch, and obtaining fresh native RED for the normal test-only commit before production adoption. + +The work used the existing interpreter and lightweight planning/source checks. It introduced no dependency-install cost. To roll back this amendment, restore the four initial quality-repair/before copies, preserving their inherited floor fixture. No rollback was performed. + +Next actions remain root-owned. Record this AI technical verdict and its exact bindings, retain the real informational dispositions, make the normal test-only commit while holding the floor hunk, obtain the required native RED, then continue the separately authorized production work. The independent review itself is complete. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json index 11d0ef0c..15979946 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json @@ -1,9 +1,9 @@ { "schema_version": "1", "decision": "accepted", - "reviewer_id": "Codex:/root/release_advisory_mapping_review (gpt-6.1-sol)", + "reviewer_id": "Codex:/root/quality_mapping_acceptance (gpt-6.1-sol)", "reviewer_role": "independent AI technical mapping reviewer", - "recorded_at": "2026-10-22T22:09:38.527915+00:00", - "reference": "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-mapping-review.md", + "recorded_at": "2026-10-22T22:39:57.741951+00:00", + "reference": "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/quality-mapping-review.md", "mapping_digest": "sha256:0788c6326b39842e80a8cf4e308ac8508c754c384d9e6479b5cd61f0ca8b1c1d" } diff --git a/openspec/changes/isolate-semgrep-security-runtime/tasks.md b/openspec/changes/isolate-semgrep-security-runtime/tasks.md index ced3ef43..9e71083b 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/tasks.md +++ b/openspec/changes/isolate-semgrep-security-runtime/tasks.md @@ -42,3 +42,16 @@ The earlier 23-case acceptance and native RED are complete historical evidence. Their bytes do not approve this newly changed specification or tests. Review thread PRRT_kwDOQLaPE86vWprX correctly identified the stale status statements; they are reconciled here and in CHANGE_VALIDATION.md and TDD_EVIDENCE.md. + +## Current quality amendment on 2026-10-23 + +- [x] Resolve every warning in the actual complete PR review without weakening assertions or tools. +- [x] Retain six visible informational findings with independently accepted source inspections. +- [x] Refresh the exact 31 local RED cases and pass all 23 supplementary controls. +- [x] Record fresh independent acceptance in requirements-proof/quality-mapping-review.md. +- [ ] Commit the test-only amendment with normal hooks, holding the docs floor for its matching metadata. +- [ ] Authenticate fresh native RED for these exact test bytes before production adoption. + +The earlier advisory acceptance remains authentic history. The active receipt now +references the fresh quality amendment acceptance. No native proof is reused for +changed test bytes. Only the docs-floor fixture hunk remains held for production. diff --git a/tests/integration/scripts/test_runtime_discovery_smoke.py b/tests/integration/scripts/test_runtime_discovery_smoke.py index bd3c89aa..66e256ba 100644 --- a/tests/integration/scripts/test_runtime_discovery_smoke.py +++ b/tests/integration/scripts/test_runtime_discovery_smoke.py @@ -106,7 +106,6 @@ def test_runtime_discovery_smoke_direct_launcher(tmp_path: Path) -> None: check=False, ) - print(result.stdout) assert sorted((dist.metadata["Name"], dist.version) for dist in distributions()) == parent_packages assert result.returncode == 0, result.stdout assert "runtime-discovery smoke passed for launcher=direct" in result.stdout diff --git a/tests/unit/docs/test_docs_validation_scripts.py b/tests/unit/docs/test_docs_validation_scripts.py index 37f3febc..ec3ba4d3 100644 --- a/tests/unit/docs/test_docs_validation_scripts.py +++ b/tests/unit/docs/test_docs_validation_scripts.py @@ -4,6 +4,7 @@ import re import textwrap from pathlib import Path +from types import ModuleType import pytest @@ -22,69 +23,49 @@ def test_docs_json_gem_uses_the_patched_security_floor() -> None: assert tuple(int(part) for part in match.group(1).split(".")) >= (2, 19, 9) -def _load_check_docs_commands() -> object: - path = REPO_ROOT / "scripts" / "check-docs-commands.py" - spec = importlib.util.spec_from_file_location("check_docs_commands", path) +def _load_script(stem: str) -> ModuleType: + path = REPO_ROOT / "scripts" / f"{stem}.py" + spec = importlib.util.spec_from_file_location(stem.replace("-", "_"), path) assert spec and spec.loader mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod -def _load_check_cross_site_links() -> object: - path = REPO_ROOT / "scripts" / "check-cross-site-links.py" - spec = importlib.util.spec_from_file_location("check_cross_site_links", path) - assert spec and spec.loader - mod = importlib.util.module_from_spec(spec) - spec.loader.exec_module(mod) - return mod - - -def _load_check_command_contract() -> object: - path = REPO_ROOT / "scripts" / "check-command-contract.py" - spec = importlib.util.spec_from_file_location("check_command_contract", path) - assert spec and spec.loader - mod = importlib.util.module_from_spec(spec) - spec.loader.exec_module(mod) - return mod - - -def test_collect_specfact_commands_from_markdown_code_block() -> None: - mod = _load_check_docs_commands() - text = """ -```bash -$ specfact backlog ceremony standup -``` -""" - cmds = mod.collect_specfact_commands_from_text(text) - assert ["backlog", "ceremony", "standup"] in cmds - - -def test_collect_specfact_commands_chained_with_and() -> None: - mod = _load_check_docs_commands() - text = """ -```bash -specfact init && specfact module list -``` -""" - cmds = mod.collect_specfact_commands_from_text(text) - assert ["init"] in cmds - assert ["module", "list"] in cmds - - -def test_tokens_from_line_preserves_flags_for_order_validation() -> None: - mod = _load_check_docs_commands() - text = """ -```bash -specfact backlog analyze-deps --json -``` -""" - cmds = mod.collect_specfact_commands_from_text(text) - assert ["backlog", "analyze-deps", "--json"] in cmds +@pytest.mark.parametrize( + ("text", "expected_commands"), + [ + pytest.param( + "\n```bash\n$ specfact backlog ceremony standup\n```\n", + [["backlog", "ceremony", "standup"]], + id="code-block", + ), + pytest.param( + "\n```bash\nspecfact init && specfact module list\n```\n", + [["init"], ["module", "list"]], + id="chained-and", + ), + pytest.param( + "\n```bash\nspecfact backlog analyze-deps --json\n```\n", + [["backlog", "analyze-deps", "--json"]], + id="flags", + ), + pytest.param( + "\n```bash\nspecfact --mode copilot import from-code legacy-api --repo . --confidence 0.7\n```\n", + [["import", "from-code", "legacy-api", "--repo", ".", "--confidence", "0.7"]], + id="leading-global-options", + ), + ], +) +def test_collect_specfact_commands_from_markdown_code_block(text: str, expected_commands: list[list[str]]) -> None: + mod = _load_script("check-docs-commands") + commands = mod.collect_specfact_commands_from_text(text) + for command in expected_commands: + assert command in commands def test_code_import_options_after_bundle_are_rejected() -> None: - mod = _load_check_docs_commands() + mod = _load_script("check-docs-commands") ok, message = mod.validate_command_tokens(["code", "import", "legacy-api", "--repo"]) @@ -94,7 +75,7 @@ def test_code_import_options_after_bundle_are_rejected() -> None: def test_code_import_explicit_subcommand_keeps_legacy_bundle_position() -> None: - mod = _load_check_docs_commands() + mod = _load_script("check-docs-commands") ok, message = mod.validate_command_tokens(["code", "import", "from-code", "legacy-api", "--repo", "."]) @@ -103,7 +84,7 @@ def test_code_import_explicit_subcommand_keeps_legacy_bundle_position() -> None: def test_generated_contract_rejects_unknown_subcommand() -> None: - mod = _load_check_docs_commands() + mod = _load_script("check-docs-commands") ok, message = mod.validate_command_tokens(["generated-contract", "typo-subcmd", "--help"]) @@ -113,7 +94,7 @@ def test_generated_contract_rejects_unknown_subcommand() -> None: def test_command_contract_retries_parent_help_for_code_import_alias(monkeypatch: pytest.MonkeyPatch) -> None: - mod = _load_check_command_contract() + mod = _load_script("check-command-contract") calls: list[tuple[list[str], list[str]]] = [] def fake_invoke( @@ -146,7 +127,7 @@ def fake_invoke( def test_placeholder_command_examples_are_ignored() -> None: - mod = _load_check_docs_commands() + mod = _load_script("check-docs-commands") ok, message = mod.validate_command_tokens(["", "--help"]) @@ -155,30 +136,19 @@ def test_placeholder_command_examples_are_ignored() -> None: def test_core_cli_modes_page_is_not_excluded_from_command_validation() -> None: - mod = _load_check_docs_commands() + mod = _load_script("check-docs-commands") assert "docs/core-cli/modes.md" not in mod._EXCLUDED_DOC_PATHS def test_github_prompt_library_is_excluded_from_command_validation() -> None: - mod = _load_check_docs_commands() + mod = _load_script("check-docs-commands") assert mod._should_skip_guidance_path(Path(".github/prompts/specfact.02-plan.prompt.md")) is True -def test_tokens_skip_leading_global_options_before_subcommand() -> None: - mod = _load_check_docs_commands() - text = """ -```bash -specfact --mode copilot import from-code legacy-api --repo . --confidence 0.7 -``` -""" - cmds = mod.collect_specfact_commands_from_text(text) - assert ["import", "from-code", "legacy-api", "--repo", ".", "--confidence", "0.7"] in cmds - - -def test_collect_specfact_commands_from_guidance_text_handles_inline_and_yaml() -> None: - mod = _load_check_docs_commands() +def test_collect_specfact_commands_from_guidance_text_supports_inline_and_yaml() -> None: + mod = _load_script("check-docs-commands") text = """ guidance: "Run `specfact module list --show-origin` before editing." steps: @@ -190,7 +160,7 @@ def test_collect_specfact_commands_from_guidance_text_handles_inline_and_yaml() def test_scan_guidance_templates_validates_resource_templates(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: - mod = _load_check_docs_commands() + mod = _load_script("check-docs-commands") monkeypatch.setattr(mod, "_REPO_ROOT", tmp_path) monkeypatch.setattr(mod, "_ADDITIONAL_GUIDANCE_ROOTS", (tmp_path / "resources",)) monkeypatch.setattr( @@ -210,14 +180,14 @@ def test_scan_guidance_templates_validates_resource_templates(tmp_path: Path, mo def test_cross_site_url_stops_at_markdown_delimiters() -> None: - mod = _load_check_cross_site_links() + mod = _load_script("check-cross-site-links") line = "| `https://modules.specfact.io/foo/bar/` |" urls = mod._urls_from_line(line) assert urls == ["https://modules.specfact.io/foo/bar/"] def test_validate_nav_targets_accepts_known_routes(tmp_path: Path) -> None: - mod = _load_check_docs_commands() + mod = _load_script("check-docs-commands") mod._REPO_ROOT = tmp_path docs_root = tmp_path / "docs" @@ -264,7 +234,7 @@ def test_validate_nav_targets_accepts_known_routes(tmp_path: Path) -> None: def test_validate_nav_targets_reports_unknown_route(tmp_path: Path) -> None: - mod = _load_check_docs_commands() + mod = _load_script("check-docs-commands") mod._REPO_ROOT = tmp_path docs_root = tmp_path / "docs" diff --git a/tests/unit/scripts/test_security_audit_gate.py b/tests/unit/scripts/test_security_audit_gate.py index 3053e0a6..a1f3da90 100644 --- a/tests/unit/scripts/test_security_audit_gate.py +++ b/tests/unit/scripts/test_security_audit_gate.py @@ -13,7 +13,7 @@ import pytest -def _load_gate_mod(): +def _load_gate_mod() -> ModuleType: root = Path(__file__).resolve().parents[3] path = root / "scripts" / "security_audit_gate.py" assert path.is_file(), path @@ -25,17 +25,17 @@ def _load_gate_mod(): @pytest.fixture -def gate_mod(): +def gate_mod() -> ModuleType: return _load_gate_mod() -def test_cvss_for_vuln_reads_numeric_fields(gate_mod) -> None: +def test_cvss_for_vuln_reads_numeric_fields(gate_mod: ModuleType) -> None: """Nested CVSS-like keys should contribute to the max score.""" vuln = {"id": "TEST-1", "severity": {"score": 8.2}} assert gate_mod._cvss_for_vuln(vuln) == pytest.approx(8.2) -def test_main_passes_when_no_vulnerabilities(gate_mod, capsys) -> None: +def test_main_passes_when_no_vulnerabilities(gate_mod: ModuleType, capsys: pytest.CaptureFixture[str]) -> None: payload = {"dependencies": [{"name": "requests", "version": "2.0.0", "vulns": []}]} proc = MagicMock(stdout=json.dumps(payload), stderr="", returncode=0) with patch.object(gate_mod.subprocess, "run", return_value=proc): @@ -43,7 +43,7 @@ def test_main_passes_when_no_vulnerabilities(gate_mod, capsys) -> None: assert "passed" in capsys.readouterr().out.lower() -def test_main_warns_when_cvss_below_threshold(gate_mod, capsys) -> None: +def test_main_warns_when_cvss_below_threshold(gate_mod: ModuleType, capsys: pytest.CaptureFixture[str]) -> None: payload = { "dependencies": [ { @@ -60,7 +60,7 @@ def test_main_warns_when_cvss_below_threshold(gate_mod, capsys) -> None: assert "ACTION REQUIRED" in out -def test_main_fails_when_cvss_at_or_above_threshold(gate_mod, capsys) -> None: +def test_main_fails_when_cvss_at_or_above_threshold(gate_mod: ModuleType, capsys: pytest.CaptureFixture[str]) -> None: payload = { "dependencies": [ { @@ -76,13 +76,15 @@ def test_main_fails_when_cvss_at_or_above_threshold(gate_mod, capsys) -> None: assert "ACTION REQUIRED" in capsys.readouterr().out -def test_main_fail_closed_on_empty_stdout(gate_mod) -> None: +def test_main_fail_closed_on_empty_stdout(gate_mod: ModuleType) -> None: proc = MagicMock(stdout="", stderr="pip-audit failed\n", returncode=2) with patch.object(gate_mod.subprocess, "run", return_value=proc): assert gate_mod.main() == 1 -def test_main_fails_when_strict_audit_skips_a_dependency(gate_mod, capsys) -> None: +def test_main_fails_when_strict_audit_skips_a_dependency( + gate_mod: ModuleType, capsys: pytest.CaptureFixture[str] +) -> None: """A strict audit cannot report success when any dependency was skipped.""" payload = {"dependencies": [{"name": "opaque", "skip_reason": "could not audit"}]} proc = MagicMock(stdout=json.dumps(payload), stderr="", returncode=1) @@ -91,7 +93,9 @@ def test_main_fails_when_strict_audit_skips_a_dependency(gate_mod, capsys) -> No assert "skipped dependency" in capsys.readouterr().err.lower() -def test_main_fails_when_strict_audit_returns_an_unexplained_error(gate_mod, capsys) -> None: +def test_main_fails_when_strict_audit_returns_an_unexplained_error( + gate_mod: ModuleType, capsys: pytest.CaptureFixture[str] +) -> None: """A nonzero strict-audit status without advisories is an audit failure.""" proc = MagicMock(stdout=json.dumps({"dependencies": []}), stderr="resolver failed", returncode=1) with patch.object(gate_mod.subprocess, "run", return_value=proc): @@ -99,12 +103,12 @@ def test_main_fails_when_strict_audit_returns_an_unexplained_error(gate_mod, cap assert "strict audit failed" in capsys.readouterr().err.lower() -def test_main_fail_closed_when_pip_audit_unavailable(gate_mod) -> None: +def test_main_fail_closed_when_pip_audit_unavailable(gate_mod: ModuleType) -> None: with patch.object(gate_mod.subprocess, "run", side_effect=FileNotFoundError("pip-audit not found")): assert gate_mod.main() == 1 -def test_dependencies_from_pip_audit_json_accepts_wrapped_and_bare_list(gate_mod) -> None: +def test_dependencies_from_pip_audit_json_accepts_wrapped_and_bare_list(gate_mod: ModuleType) -> None: wrapped = {"dependencies": [{"name": "a", "version": "1", "vulns": []}], "fixes": []} assert gate_mod._dependencies_from_pip_audit_json(wrapped) == wrapped["dependencies"] bare = [{"name": "b", "version": "2", "vulns": []}] @@ -113,7 +117,7 @@ def test_dependencies_from_pip_audit_json_accepts_wrapped_and_bare_list(gate_mod assert gate_mod._dependencies_from_pip_audit_json({"dependencies": "nope"}) is None -def test_main_passes_with_top_level_list_json(gate_mod, capsys) -> None: +def test_main_passes_with_top_level_list_json(gate_mod: ModuleType, capsys: pytest.CaptureFixture[str]) -> None: payload = [{"name": "requests", "version": "2.0.0", "vulns": []}] proc = MagicMock(stdout=json.dumps(payload), stderr="", returncode=0) with patch.object(gate_mod.subprocess, "run", return_value=proc): @@ -121,7 +125,7 @@ def test_main_passes_with_top_level_list_json(gate_mod, capsys) -> None: assert "passed" in capsys.readouterr().out.lower() -def test_main_audits_the_frozen_requirements_file_in_strict_mode(gate_mod) -> None: +def test_main_audits_the_frozen_requirements_file_in_strict_mode(gate_mod: ModuleType) -> None: payload = {"dependencies": [{"name": "requests", "version": "2.0.0", "vulns": []}]} proc = MagicMock(stdout=json.dumps(payload), stderr="", returncode=0) with patch.object(gate_mod.subprocess, "run", return_value=proc) as run_mock: @@ -134,12 +138,14 @@ def test_main_audits_the_frozen_requirements_file_in_strict_mode(gate_mod) -> No assert "--skip-editable" not in cmd -def test_main_rejects_an_unexpected_argument(gate_mod) -> None: +def test_main_rejects_an_unexpected_argument(gate_mod: ModuleType) -> None: with pytest.raises(SystemExit, match="2"): gate_mod.main(["--unknown"]) -def test_main_allows_only_a_matching_unexpired_reviewed_exception(gate_mod, tmp_path: Path, capsys) -> None: +def test_main_allows_only_a_matching_unexpired_reviewed_exception( + gate_mod: ModuleType, tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: """A non-upgradable transitive advisory needs an exact, short-lived review record.""" payload = { "dependencies": [ diff --git a/tests/unit/security/test_release_promotion_security_gates.py b/tests/unit/security/test_release_promotion_security_gates.py index e29c87a8..a0676786 100644 --- a/tests/unit/security/test_release_promotion_security_gates.py +++ b/tests/unit/security/test_release_promotion_security_gates.py @@ -423,8 +423,13 @@ def test_frozen_graph_uses_official_semgrep_with_bounded_mcp_exception() -> None for record, expected_record in zip( sorted(records, key=lambda entry: entry["package"]), expected_records, strict=True ): - actual_record = {key: record[key] for key in expected_record} - actual_record["vulnerability_ids"] = sorted(record["vulnerability_ids"]) + actual_record = { + "package": record["package"], + "version": record["version"], + "vulnerability_ids": sorted(record["vulnerability_ids"]), + "reviewed_on": record["reviewed_on"], + "expires_on": record["expires_on"], + } assert actual_record == expected_record assert tuple(bool(record[key].strip()) for key in ("rationale", "mitigation")) == (True, True) lock = tomllib.loads((REPO_ROOT / "uv.lock").read_text(encoding="utf-8")) @@ -537,7 +542,7 @@ def test_proof_executor_imports_installed_pytest_before_repository_root(tmp_path assert bootstrap.index("import pytest") < bootstrap.index("sys.path.append(repo_root)") -def test_invalid_utf8_authority_is_stable_metadata_failure(tmp_path: Path) -> None: +def test_invalid_utf8_authority_rejects_malformed_bytes(tmp_path: Path) -> None: """Malformed external bytes fail closed without exposing parser details.""" comment = tmp_path / "comment.json" comment.write_bytes(b"\xff") From e90628d76ab9e241e77e451a5d83647c13634f28 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:54:13 +0000 Subject: [PATCH 7/7] test(security): bind supported parser proof Stage: prefix7 Source: 2eef82529a8eb166b9a18898316ae92ce170f02a Ordinal: 7 Manifest: 6c3d344ecb69beca59231c3c49ae05dba50790fd253db87f37122028de1dfa2a Executor: bd639eb85d78f5a3d3438893297b31f5b3a93e88 Run: 39949294200 Attempt: 1 --- .../CHANGE_VALIDATION.md | 38 +++- .../TDD_EVIDENCE.md | 130 ++++++++++- .../design.md | 44 +++- .../proposal.md | 42 +++- .../requirements-evidence.yaml | 36 +++ .../advisory-correction-review-pending.md | 10 +- .../parser-mapping-review.md | 130 +++++++++++ .../pre-trust-review-evidence.json | 9 + .../requirements-proof/review-evidence.json | 8 +- .../trust-correction-review-pending.md | 20 ++ .../specs/isolated-scanner-runtime/spec.md | 16 +- .../isolate-semgrep-security-runtime/tasks.md | 36 ++- .../scripts/test_runtime_discovery_smoke.py | 2 + .../scripts/test_dependency_trust_review.py | 205 +++++++++--------- .../test_release_dependency_selection.py | 1 + 15 files changed, 595 insertions(+), 132 deletions(-) create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/parser-mapping-review.md create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-trust-review-evidence.json create mode 100644 openspec/changes/isolate-semgrep-security-runtime/requirements-proof/trust-correction-review-pending.md diff --git a/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md b/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md index d5193b96..80b9fd82 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md +++ b/openspec/changes/isolate-semgrep-security-runtime/CHANGE_VALIDATION.md @@ -15,7 +15,8 @@ release selects nine changed Python packages relative to main. That correction r The owner separately approved GitPython 3.2.1 and the exact opentelemetry-api 1.37.0 exception on 2026-10-22. Fresh independent mapping acceptance for this new correction is recorded in -requirements-proof/advisory-mapping-review.md. Only new native RED remains pending. All twelve saved production paths +requirements-proof/advisory-mapping-review.md. That advisory acceptance was later superseded by the accepted quality amendment +and authenticated native RED described below. All twelve saved production paths were verified and backed up before restoration to HEAD. The four independently reviewed fixture corrections were preserved, then received the bounded test-only quality amendment recorded in requirements-proof/quality-mapping-review.md. The coordinating agent owns @@ -24,6 +25,37 @@ the matching internal wiki update and has reported its graph rebuild complete. On 2026-10-23 the coordinator refreshed issue metadata and verified OPEN/Todo, labels and project assignment. Fresh independent AI technical acceptance covers the quality amendment, unchanged canonical 31-case plan, genuine local RED and -23 passing controls. The active receipt references quality-mapping-review.md. +23 passing controls. That historical receipt references quality-mapping-review.md. The docs floor hunk is excluded from the test-only commit and held for matching -production metadata. Fresh native RED remains required before adoption. +production metadata. That requirement was subsequently satisfied for those exact 31-case bytes, as +recorded below. The new parser correction still requires its own proof. + +Native RED was authenticated for commit 4936750186fcb339913a8710f3034cf1d2be901f, +run 39866724812 attempt 1 and artifact 12463204671. All 31 selected cases fail for +their intended assertions with no errors or skips. The artifact digest and exact +source/tree/test/plan identities match. That opened only the then-authorized production +unit. The parser correction below changes test bytes and closes adoption again. +Local and native GREEN, independent production review and all final gates remain required. + +## Supported parser correction validation on 2026-10-23 Europe/Berlin + +The coordinating agent authorized the ordinary supported pycparser 3.12 upgrade +and factual source review under the current repair grant. This adds one official +package update to the prior nine. The twelve production paths were restored to +4936750186fcb339913a8710f3034cf1d2be901f before this specification/test unit. +The trust register, checker, toolchain, signatures and held docs floor are frozen. + +The active receipt references parser-mapping-review.md, which records fresh +independent AI acceptance of all 33 selected cases and genuine local RED with +33 passing controls. Native RED for these changed bytes remains pending. The quality acceptance +and authenticated 31-case native RED remain history under their exact identities. +The review date is 2026-10-22 UTC, equivalent to the observed review session on +2026-10-23 Europe/Berlin. Expiry is 2026-11-04 inclusive. Real-clock enforcement +and the hard-blocked 3.0 family remain unchanged. Root owns the internal wiki +update, independent acceptance and subsequent production verification. + +The internal wiki mirror is pending authorization. Automatic approval review +rejected the coordinator's sibling-repository write as outside the authorized PR +and worktree scope. No wiki write or graph rebuild ran for this correction. +The proposed patch is retained at +`/private/tmp/specfact-release-20261022/audit/internal-wiki-parser-correction.patch`. diff --git a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md index 9d0a0eec..a8d2572d 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md +++ b/openspec/changes/isolate-semgrep-security-runtime/TDD_EVIDENCE.md @@ -2,6 +2,12 @@ ## Current proof status +The supported pycparser 3.12 correction has fresh independent AI technical +acceptance and genuine local RED for all 33 selected cases, with 33 passing +supplementary controls. Fresh native RED remains required before production. +The active receipt points to `requirements-proof/parser-mapping-review.md`. Earlier measured +results remain historical and supply no acceptance for the changed bytes. + The prior 23-case correction received independent mapping acceptance and native RED at commit 8cce44f449e9559d0100064d629c81991784ab41, run 39843165302 attempt 1. Its accepted receipt is preserved in `requirements-proof/pre-advisory-review-evidence.json`. @@ -12,10 +18,13 @@ Production inputs were restored before the specification and test changes. The 31-case advisory mapping first received independent acceptance in `requirements-proof/advisory-mapping-review.md`. A subsequent bounded test-only quality amendment received fresh independent AI technical acceptance in -`requirements-proof/quality-mapping-review.md` on 2026-10-23. The active receipt -uses that repository-relative report. Both earlier receipts remain historical. -Fresh native RED for the amended test bytes remains pending. No earlier native -proof approves these changed bytes. The docs floor fixture remains held for its +`requirements-proof/quality-mapping-review.md` on 2026-10-23. The historical quality receipt +uses that repository-relative report and is preserved in +`requirements-proof/pre-trust-review-evidence.json`. Earlier receipts remain historical. +Fresh native RED for the amended test bytes is authenticated at commit +4936750186fcb339913a8710f3034cf1d2be901f, run 39866724812 attempt 1, artifact +12463204671. All 31 cases fail with no errors or skips. No earlier native proof +approves these changed bytes. The docs floor fixture remains held for its matching production metadata, separate from the accepted test-helper repair. ## Superseded 23-case pending note @@ -485,3 +494,116 @@ Normal partial staging retains the old docs-floor assertion in this test-only commit. Its already-reviewed new floor stays unstaged for the matching Gemfile adoption. The four Markdown examples, five memberships and fresh loader behavior are staged together. Native RED is still required before production adoption. + +## Authenticated quality-amendment native RED on 2026-10-23 + +GitHub run 39866724812 attempt 1 belongs to PR785's current test-only commit +`4936750186fcb339913a8710f3034cf1d2be901f`, tree +`a95395aaf17130d8c7835fe2ee19d3e3c251f818`, main base +`5262efb0881e4fac7697d629b0530250342bc656`. Artifact 12463204671 is named +requirements-evidence and is unexpired. Its downloaded ZIP SHA-256 matches the +GitHub API digest `11744aa3911b4583a6ad116a9ac101de4bf18c2412d58a28d55d919c0c4fefdf`. +The four bounded archive members have unique safe paths and no symbolic links. + +The native report states passed, gate pass, red and failing-first-proven, with +no findings. The XML has exactly 31 genuine failures, zero errors and zero skips. +Its SHA-256 is `ed49f886df8d0c6dd0924ca9666b76fb9ba021f33758a8801ee94dae23ea8abe`. +Root inspected all failure messages and the substantive Git/source-build traces. +The source build reaches the backend and fails on the absent bundle-mapper +forced inclusion. Git reaches only mocked clone sentinels. No setup failure is +counted as a successful behavioral reproduction. + +Source commit, complete tree, merge base, all seven test-file digests and all +31 unique selectors match current reviewed source and the genuine XML. The full +native plan equals the independently reviewed planned plan except the two +expected maturity fields, which advance from planned to test-authored. All +canonical identities and inner cases remain equal. An exploratory local +standalone test-authored planner invocation without its acceptance input refused +acceptance-missing. It is retained as a command limitation, not used as proof. +No machine report or artifact was edited to produce this comparison. + +Exact metadata, downloaded archive, extracted native files and root identity +record remain in `/private/tmp/specfact-release-20261022/quality-red/`, including +native-ready.json. Normal test-only hooks passed with the docs-floor hunk held. +That proof authorized the prior twelve-path candidate. The parser correction +below requires its own fresh native proof before production. Root freezes all +other tracked content, including mapped tests, conftest and transitive imports. Final local and native GREEN, independent production review, +current-head authority, all required CI and Linux frozen Ruby proof remain open. + +## Supported parser test preparation on 2026-10-23 Europe/Berlin + +The twelve production paths remain restored to 4936750186fcb339913a8710f3034cf1d2be901f. +The active mapping adds pycparser 3.12 lock/export selection and its exact provenance +record, giving 33 selectors. All prior 31 selector identities remain. The wrong-hash +and wrong-package fixtures use the record's version and review date solely to isolate +the identity rejection. The actual-date checker and all hard-block/expiry controls +are unchanged. No mapped pytest was run before independent acceptance. + +The controlled smoke probe executes the existing fixture's environment-construction +statements and launches real Python in a disposable environment. Before correction, +the sentinel case imported the parent-only module, and the owner's default environment +exposed repository src on sys.path. Both child processes inherited PYTHONHOME. +The isolation check exited 1. Removing only PYTHONPATH and PYTHONHOME makes both +observations false and both environment values absent; the unchanged probe exits 0. +This is a child-interpreter environment proof, not a full bootstrap/CLI smoke result. +All original bootstrap, CLI success and parent package inventory assertions remain. + +Actual before/after JSON and logs, probe source, exact source hashes, command lists, +and the prepared mapping are retained under +`/private/tmp/specfact-release-20261022/trust-correction-tests/`. +Fresh independent acceptance and local RED are recorded below. Native RED and +later full smoke execution remain required. Production, trust checker/register, Code Review toolchain and signed +assets were not edited. The held docs-floor hunk was preserved byte for byte. + +## Published companion execution boundary + +The coordinator's read-only trace is retained at +`/private/tmp/specfact-release-20261022/audit/semgrep-contract-trace.md`. +The measured 1.179.0 CLI positive/negative probes and schema 1.2 Code Review result +exercise pinned companion fixture 0.48.15 through ordinary PATH-based passes. +They are not proof of current signed-capsule admission. Published main 0.51.2 +and dev 0.51.4 use isolated capsules with `-I -S`, cleared environment, dedicated +PATH, and expected capsule analyzer versions. Failure returns UNKNOWN without +core analyzer fallback. The trace inspected both published archives and verified +their registry hashes. It did not verify their signatures or execute the capsules. + +Review comments 4354335589 and 4354335598 correctly identify the companion's admitted +versions, but their claimed core-upgrade-to-capsule mismatch is unsupported by the +inspected execution path. A future capsule version change requires a separate +companion policy/integrity change. No such asset or gate is changed by this release. + +## Accepted parser mapping and local RED on 2026-10-23 Europe/Berlin + +Independent AI technical reviewer `/root/parser_mapping_acceptance` accepted the +exact mapping, source correspondence, controlled fixture changes and actual local +proof in `requirements-proof/parser-mapping-review.md`. This is technical +acceptance, without human risk, release or merge authority. All original 31 +selectors remain and only the exact parser selection and provenance cases are added. +The source mapping digest is `sha256:b192853e22bfc854a30366891d76942d5c2ba9cc47260197c3c4df7bd77c923c`. +The combined digest is `sha256:7752d805c6911e719e9aa3dcb985526f8b10f3e587ebfb1d37c5e67c5703718f`. +The plan identity is `sha256:105b01868f04e3d382b9266bc38111825ca06ad0d00a42d477ac8f5e322e805b`. + +Actual final local execution on 2026-10-22 at 23:23 UTC, equivalent to +2026-10-23 at 01:23 Europe/Berlin, produced 33 failures, zero errors and zero +skips. All 33 supplementary controls passed. XML SHA-256 values are +`99b63cef7ee46814a86ca3e66f1c2bebffc280e72e113389a2990b0c659fd697` +and `ac3479cf77e1dbcc6ff99744fbc58bb352d6115a55f036f3f3d3c0eec61f7686`. +Root and the independent reviewer inspected every failure message and the actual +source-build, certificate, JWT and Git clone-boundary traces. No setup failure +was counted as behavioral RED. Earlier pre-comment execution remains historical. + +The actual final whole-PR Code Review covers 13 Python paths. Its unmodified +report has zero errors, zero warnings and six visible informational findings with +fresh source-specific preservation reasons in the independent report. Initial +three duplication warnings prompted literal parameter tables for five unmapped +negative fixtures; all inputs, dates, expected errors and expanded AST bodies +remain equivalent. Two redundant new docstrings were removed after a separate +comment review. No suppression, policy change or report normalization occurred. +Final trust-test SHA-256 is `bb20822cb58c8f773f8ed7a54628feef2e1eadbf5cc5c53936422223986ee7ac`. +Actual outputs, commands, hashes and historical reports are retained under +`/private/tmp/specfact-release-20261022/trust-correction-tests/`. + +Production paths and the trust register/checker remain restored and unchanged. +Normal test-only hooks, a fresh native 33-case RED artifact, the pending internal +wiki scope decision, production adoption and full local/native GREEN remain. +The held docs-floor assertion stays with its later matching production metadata. diff --git a/openspec/changes/isolate-semgrep-security-runtime/design.md b/openspec/changes/isolate-semgrep-security-runtime/design.md index db05994c..b00f0807 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/design.md +++ b/openspec/changes/isolate-semgrep-security-runtime/design.md @@ -22,7 +22,8 @@ wheel. The later fix adds only the missing source inclusion. The full intended production touchpoints are pyproject.toml, uv.lock, requirements/ci/locked.txt, ci/vulnerability-audit-exceptions.json, setup.py, src/__init__.py, src/specfact_cli/__init__.py, CHANGELOG.md, docs/Gemfile, -docs/Gemfile.lock, and scripts/check_jekyll_dependencies.rb and .github/workflows/docs-dependency-checks.yml. +docs/Gemfile.lock, scripts/check_jekyll_dependencies.rb, .github/workflows/docs-dependency-checks.yml, +and ci/dependency-trust-exceptions.json. The Ruby probe runs against installed locked gems after the dependency update. Its output is supplementary compatibility evidence, not a substitute for native mapped Python evidence. @@ -52,7 +53,7 @@ No artifact behavior assertion, negative control, or native policy is weakened. ## Corrected dependency selection and review boundary -The existing parameter table binds the eight exact runtime and tooling versions +The existing parameter table binds the nine exact runtime and tooling versions to both uv.lock and its hashed export. The separate scanner case binds official Semgrep 1.179.0 and MCP 1.29.0. Artifact probes continue to inspect the actual frozen certifi, GitPython, and PyJWT wheels. Exact-version assertions complement @@ -81,7 +82,40 @@ expiry tests remain unchanged. The scanner graph contract now requires exactly two separate records and official opentelemetry-api 1.37.0. No gate code changes. The accepted 23-case receipt is preserved as `pre-advisory-review-evidence.json`. -The active receipt records fresh independent AI technical acceptance in -`requirements-proof/advisory-mapping-review.md`; new native RED remains pending. The new -plan includes only observed baseline failures. Supplementary controls must pass. +The advisory receipt and `requirements-proof/advisory-mapping-review.md` are historical. +The later quality amendment received acceptance in `requirements-proof/quality-mapping-review.md` +and authenticated 31-case native RED at commit 4936750186fcb339913a8710f3034cf1d2be901f, +run 39866724812 attempt 1. Its receipt is retained as `pre-trust-review-evidence.json`. +The active parser-correction receipt references `requirements-proof/parser-mapping-review.md` +for fresh independent AI acceptance and local 33-case RED. Native RED remains pending. +Earlier proof applies only to its original bytes. Supplementary controls must pass. Root owns independent acceptance, native RED, wiki synchronization, and production. + +## Supported parser and current review boundary + +The existing version table gains pycparser 3.12. The existing provenance-record +case binds its exact official wheel URL, SHA-256, classification, UTC review date, +and bounded expiry. These two selectors extend the 31-case plan to 33 cases. +The record's future implementation uses the existing JSON shape and records the +immutable upstream source, matching BSD license, disclosed Socket capabilities, +and license-heading classification in its rationale. No checker changes are needed. +The 2026-10-22 review date is the actual UTC day of the 2026-10-23 Berlin review, +so the unchanged date.today() check also works on native UTC runners. + +The wrong-hash and wrong-package fixtures derive only their baseline version and +review date from the actual reviewed record. Their expected rejection conditions +remain fixed. Explicit expiry, prohibited-wheel and 3.0-alias tests remain intact. +The existing real-clock checker test continues to execute without an injected date. + +Review comment 4354335605 identifies inherited PYTHONPATH and PYTHONHOME in the +otherwise disposable direct-launcher smoke environment. A controlled child-Python +probe must first demonstrate that inheritance. The fixture then removes only those +two keys before its existing bootstrap and actual CLI calls. Parent package inventory +and every existing success assertion remain intact. This test-fixture correction +requires a later full real smoke run and does not change application behavior. + +Review comment 4354335583 identifies stale active acceptance wording. The earlier +advisory and quality reports remain history, including the authentic 31 native +failures. The changed 33-case plan now has independent AI acceptance and local RED +with 33 passing controls. Root owns native execution and the pending authorized +internal wiki mirror update. diff --git a/openspec/changes/isolate-semgrep-security-runtime/proposal.md b/openspec/changes/isolate-semgrep-security-runtime/proposal.md index 242895f1..5cd5c35f 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/proposal.md +++ b/openspec/changes/isolate-semgrep-security-runtime/proposal.md @@ -12,7 +12,10 @@ proof for this main branch. - Refresh official Python packages to certifi 2026.10.19, GitPython 3.2.1, Hatchling 1.32.4, PyJWT 2.15.1, urllib3 2.8.0, virtualenv 21.14.12, - python-discovery 1.6.1, packaging 26.3, and Semgrep 1.179.0. Preserve MCP 1.29.0. + python-discovery 1.6.1, packaging 26.3, pycparser 3.12, and Semgrep 1.179.0. Preserve MCP 1.29.0. +- Raise the pycparser floor in pyproject.toml and setup.py to `>=3.12,!=3.0.*`. + Replace the expired 2.22 provenance record with an exact 3.12 artifact review + dated 2026-10-22 UTC and expiring 2026-11-04 inclusive. - Bound the advisory register to two separate exact records: MCP 1.29.0 reviewed 2026-10-21 and opentelemetry-api 1.37.0 reviewed 2026-10-22. Both expire 2026-11-04 inclusive and retain only their approved advisory IDs. @@ -85,3 +88,40 @@ service accepting untrusted propagation headers. Preserve MCP 1.29.0 and its original IDs and 2026-10-21 review date. Each record remains exact and expiring. The approved scope requires fresh mapping acceptance and native RED before production adoption. The earlier 23-case acceptance and native RED remain history. + +## Supported parser correction on 2026-10-23 Europe/Berlin + +The actual-date trust gate rejects the old pycparser 2.22 record after its +2026-10-22 expiry. The supported replacement is official pycparser 3.12. +Its wheel SHA-256 is `a076f3f489b39b43ed13563a1dafe42447445bec2e294154ec5c57fcdda4c85e`. +All eight package files and the BSD-3-Clause license match upstream commit +[3ec766b7cd94724cba714296509604597ea4ce4e](https://github.com/eliben/pycparser/commit/3ec766b7cd94724cba714296509604597ea4ce4e). +The retained investigation accessed the official artifacts and exact Socket wheel +page on 2026-10-23 Europe/Berlin, during 2026-10-22 UTC. + +The source review discloses optional preprocessor shell-access capability, +filesystem access, URL strings, and a license-heading false positive. The +inspected exact wheel showed no eval, AI anomaly, obfuscation, or malware category. +This factual provenance review does not create a CVE waiver or renew the old +2.22 record. The exact MCP and OpenTelemetry exceptions remain separate. + +Normal scratch resolution changes only pycparser 2.22 to 3.12 relative to the +nine-update candidate. Measured CFFI 2.1.0 and cryptography 50.0.0 compatibility +and optional-preprocessor/file-open boundary probes pass on macOS Python 3.11.15. +Other platforms and repository release gates remain to be verified. Fresh +independent mapping acceptance and genuine native RED must precede production. + +## Scanner compatibility evidence boundary + +The retained Semgrep 1.179.0 positive/negative CLI probes and schema 1.2 Code Review +result cover the pinned companion fixture 0.48.15 and its ordinary PATH-based passes. +They do not establish admission into the current companion's signed capsule. +The published [main 0.51.2 registry](https://github.com/nold-ai/specfact-cli-modules/blob/209b4ed835187ea857d1fa847abdd351f6f6232a/registry/index.json) +and [dev 0.51.4 registry](https://github.com/nold-ai/specfact-cli-modules/blob/430e3ccbf9fbd6a6177b033662a76235a8bd6cc8/registry/index.json) +were inspected on 2026-10-23 Europe/Berlin. Their archive-derived execution paths +use isolated capsules, `-I -S`, a cleared environment, dedicated PATH, and the +capsule's expected analyzer-version map. Capsule failure returns UNKNOWN without +falling back to core Semgrep. Signature verification and capsule execution were +not performed in this investigation. A future capsule-version change belongs in +the companion repository with its own policy and CI-produced integrity updates. +This core release changes no companion gate, capsule asset, or signature. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml b/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml index 780ba844..4d1cdd0a 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml @@ -131,6 +131,14 @@ requirements: selector: runner: pytest node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[python-discovery-1.6.1] + - case_id: PYCPARSER-FROZEN-SELECTION + scenario_id: select-the-reviewed-compatible-tooling-graph + method: test + intent: Check reviewed pycparser 3.12 selection and export parity. + observable: The official registry lock version and exported requirement both match the reviewed version. + selector: + runner: pytest + node_id: tests/unit/security/test_release_dependency_selection.py::test_reviewed_tooling_versions_match_frozen_export[pycparser-3.12] openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:bound-the-temporary-official-mcp-advisory-exception: rationale: Keep both main audit lanes strict with separate exact, expiring owner-approved exceptions. stakeholder_refs: @@ -385,3 +393,31 @@ requirements: selector: runner: pytest node_id: tests/unit/scripts/test_security_audit_gate.py::test_committed_advisory_exception_is_exact_and_time_bounded[otel-future-review] + openspec:isolate-semgrep-security-runtime:isolated-scanner-runtime:bind-the-supported-parser-to-current-source-provenance: + rationale: Bind the supported parser to fresh exact source evidence without renewing the expired record or weakening trust policy. + stakeholder_refs: *id001 + touchpoints: + - id: touchpoint-1 + kind: config_file + locator: ci/dependency-trust-exceptions.json + - id: touchpoint-2 + kind: config_file + locator: uv.lock + - id: touchpoint-3 + kind: config_file + locator: pyproject.toml + - id: touchpoint-4 + kind: config_file + locator: setup.py + - id: touchpoint-5 + kind: test + locator: tests/unit/scripts/test_dependency_trust_review.py + verification_cases: + - case_id: PYCPARSER-EXACT-PROVENANCE-REVIEW + scenario_id: bind-the-supported-parser-to-current-source-provenance + method: test + intent: Read the committed exact pycparser 3.12 provenance record. + observable: The record binds the official wheel URL and SHA-256, source-provenance-reviewed classification, actual UTC review date, bounded expiry, and cryptography/CFFI path. + selector: + runner: pytest + node_id: tests/unit/scripts/test_dependency_trust_review.py::test_pycparser_has_current_exact_version_review_record diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md index accf0225..9d23a9ae 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/advisory-correction-review-pending.md @@ -1,9 +1,11 @@ # Historical advisory correction pending note -The pending note below is superseded by accepted independent AI technical -review in `advisory-mapping-review.md` on 2026-10-23. That acceptance is retained as history. The active receipt now records the -subsequent quality amendment acceptance in quality-mapping-review.md. Only new authenticated native RED remains pending before -production adoption. This does not grant human, merge or publication authority. +The original pending note below was superseded by advisory acceptance and then +quality acceptance in `quality-mapping-review.md`. The quality amendment received +authenticated 31-case native RED at commit 4936750186fcb339913a8710f3034cf1d2be901f, +run 39866724812 attempt 1. Those reports remain history. The active receipt now +points to `trust-correction-review-pending.md` for the changed 33-case parser plan. +Fresh independent acceptance and native RED remain pending for that correction. ## Original pending note diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/parser-mapping-review.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/parser-mapping-review.md new file mode 100644 index 00000000..886d9e30 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/parser-mapping-review.md @@ -0,0 +1,130 @@ +# Independent parser mapping review + +TL;DR. ACCEPTED for the test-authored mapping and genuine local failing-first evidence described here. The 33-case plan preserves all prior 31 cases and adds only exact pycparser selection and provenance. The final local run has 33 genuine failures, zero errors and zero skips. All 33 supplementary controls pass. Production adoption still requires fresh authenticated native RED for the final source bytes, followed by the required production and release checks. + +I am Codex `/root/parser_mapping_acceptance`, an independent AI technical mapping reviewer. I did not author this change, alter repository files, edit the acceptance receipt, execute mapped pytest, install dependencies, synchronize the environment, sign, commit, push, or write to GitHub. I independently ran canonical planning and inspected actual repository and retained artifact bytes. This is AI technical acceptance, without human, merge, publication, or vulnerability-waiver authority. + +Review recorded at 2026-10-23T01:25:54.973004+02:00, Europe/Berlin. Worktree is `/private/tmp/specfact-release-20261022/worktrees/hotfix/security-dependencies-0-55-11`. HEAD is `4936750186fcb339913a8710f3034cf1d2be901f`; canonical exact main base is `5262efb0881e4fac7697d629b0530250342bc656`. + +The independent canonical command used the owner environment from `/private/tmp/specfact-release-20261022/red-owner/env.sh`, `GITHUB_BASE_REF=main`, and the existing environment Python. It ran `scripts/requirements_evidence_delivery_gate.py --base-ref 5262efb0881e4fac7697d629b0530250342bc656 --required-maturity planned` with outputs solely in `trust-correction-tests/reviewer/`. Initial, refrozen and final planned gates each exited 0. The final full plan is byte-identical to the author's `plan-final.json`; observed maturity remains planned, with implementation evidence not yet available. This gate result is planning acceptance, not a green implementation result. + +The canonical identities are: + +| Identity | Digest | +| --- | --- | +| Source mapping | `sha256:b192853e22bfc854a30366891d76942d5c2ba9cc47260197c3c4df7bd77c923c` | +| Combined mapping | `sha256:7752d805c6911e719e9aa3dcb985526f8b10f3e587ebfb1d37c5e67c5703718f` | +| Plan identity | `sha256:105b01868f04e3d382b9266bc38111825ca06ad0d00a42d477ac8f5e322e805b` | + +The immutable scope and final selected test bytes at acceptance are: + +| Source | SHA-256 | +| --- | --- | +| `openspec/changes/isolate-semgrep-security-runtime/proposal.md` | `f8eee7cdcc150eb1562388f0d2290d65901895511a8ab9c1944a2f426a094049` | +| `openspec/changes/isolate-semgrep-security-runtime/design.md` | `431419ae2195fb0f7ef70ef7ce3f5d011c00f4f0d44aa92fc6080ba3f0e9e739` | +| `openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md` | `bc00f6593ec6a64bd47bd18933d1d222a34793a035be3f89542234960b056ef6` | +| `openspec/changes/isolate-semgrep-security-runtime/requirements-evidence.yaml` | `cad89859300251d78206a5291077c127e43d2caef687c29c11842e2d36851dab` | +| `tests/integration/test_distribution_build.py` | `af24d9caa1259ecf9789440c516784ea01d4afb34acf8fe9ef377f5f2f46806b` | +| `tests/unit/scripts/test_dependency_trust_review.py` | `bb20822cb58c8f773f8ed7a54628feef2e1eadbf5cc5c53936422223986ee7ac` | +| `tests/unit/scripts/test_security_audit_gate.py` | `5a0c6669c793477bc8cabe518a7f7d20e8db98bb2201be255ed241654c4ed748` | +| `tests/unit/security/test_frozen_dependency_artifacts.py` | `146f45786ccb74b581c36a1a626130c540631e45fa869433d5663e56202b4215` | +| `tests/unit/security/test_frozen_gitpython_compatibility.py` | `284563d0d316d73910c7857afbc76f083d056f803765d79382c8a54ebfedc162` | +| `tests/unit/security/test_frozen_jwt_compatibility.py` | `35b8e0d571bf50a30599ad1634b068d2d3d37e9a46ffd45fe6dfea03771cab16` | +| `tests/unit/security/test_release_dependency_selection.py` | `24d9e8555a072c306fa332e429805d1b9ce16b2f18f245dff2a42ff1d957bd09` | +| `tests/unit/security/test_release_promotion_security_gates.py` | `9f0ede9d55afa5241b42b30660bec58099945a9b636b1a7c57cda69139a10487` | + +All eight selected test hashes match the refreshed `prepared-source-hashes.json`. There are 33 unique case IDs, 33 unique pytest node IDs and exactly eight selected test files. Comparing the actual previous quality plan with this plan preserves every prior 31 node identity and adds exactly `test_pycparser_has_current_exact_version_review_record` and `test_reviewed_tooling_versions_match_frozen_export[pycparser-3.12]`. `mapping.tsv`, `selectors.json`, the canonical cases and execution command agree. The 33 supplementary selectors are unique and disjoint from mapped selectors. All previous 23 controls remain unchanged. + +Initial acceptance permitted execution before any mapped pytest body ran. After the initial full PR review surfaced three DRY warnings in newly reviewed trust-test scope, root consolidated only five unmapped negative cases into two literal pytest parameter tables. I independently substituted each row into its new function and compared it with its old function. Every fixture input, controlled date, expected error and executable statement matched the original AST. All other function source segments were byte-identical. The five renamed controls map exactly through `dry-fix/old-new-control-mapping.json`; the controls still represent all 33 original scenarios. No helper, policy bypass or suppression was introduced. + +The first revised trust-test hash was `79706595f296942cddab04e5eaae41cbe9908b66ed62582ae42ca7ca88bb5881`. A separate comment review then removed only the two redundant new table-function docstrings. I reconstructed that entire prior file exactly by adding those two lines back and verified its full SHA-256. Final source hash is `bb20822cb58c8f773f8ed7a54628feef2e1eadbf5cc5c53936422223986ee7ac`. I issued renewed execution acceptance before root reran the mapped cases. Earlier runs remain in `pre-comment-red/` and do not stand in for this final run. + +The new record test asserts exact version, complete wheel URL, complete artifact SHA-256, classification, UTC review date 2026-10-22, expiry 2026-11-04, and cryptography/CFFI/parser transitive path. The selection case reads actual lock and hashed delivery export and asserts exact version plus official registry origin. These are release invariants with direct artifact observations. They do not construct passing expected production data inside the test. + +The two wrong-artifact and wrong-package fixtures now derive only baseline version and controlled review-day input from the current record. Their mutations and exact rejection expectations remain explicit. The date injection is confined to those deterministic fixture calls. The real `-S` subprocess test still passes no date override, and the production checker still uses `today or date.today()`. The 3.0 block and equivalent spellings, explicit expiry, prohibited wheel names and lock records, scanner floor, and wrong source/hash/package controls remain present and pass locally. No CVE waiver was created. The exact MCP and OpenTelemetry contracts retain their separate IDs and 2026-11-04 inclusive expiries. + +Root's final actual execution ran the exact reviewed commands. I independently parsed both JUnit files, compared every node identity and order with its command and plan, checked uniqueness and counts, and read all 33 failure messages. I also inspected the substantive build and Git traces. + +| Evidence | Observed result | SHA-256 | +| --- | --- | --- | +| `trust-correction-tests/mapped.xml` | 33 failures, 0 errors, 0 skips; process exit 1 | `99b63cef7ee46814a86ca3e66f1c2bebffc280e72e113389a2990b0c659fd697` | +| `trust-correction-tests/controls.xml` | 33 passes, 0 failures, 0 errors, 0 skips; process exit 0 | `ac3479cf77e1dbcc6ff99744fbc58bb352d6115a55f036f3f3d3c0eec61f7686` | + +The mapped execution began at 2026-10-22T23:23:01.316715+00:00 and finished at 23:23:03.178972+00:00. Controls followed from 23:23:03.179383+00:00 through 23:23:03.878956+00:00. These are 2026-10-23 Europe/Berlin. Actual argv and exit records match `mapped-command.json` and `controls-command.json` exactly. The failures remain failures. They are accepted failing-first observations, without changing the machine outcome to pass. + +Each reviewed failure has a substantive expected cause: + +| Case | Actual disagreement | +| --- | --- | +| `PYCPARSER-EXACT-PROVENANCE-REVIEW` | Committed record or frozen selection is 2.22; required exact version is 3.12. | +| `OTEL-EXACT-CVE` | Actual audit gate returns 1 instead of the required 0 for an allowed exact advisory/date. | +| `OTEL-EXACT-GHSA` | Actual audit gate returns 1 instead of the required 0 for an allowed exact advisory/date. | +| `OTEL-EXPIRED` | Gate rejects vulnerability generally, but lacks the required invalid-date record diagnostic. | +| `OTEL-EXPIRY-DAY` | Actual audit gate returns 1 instead of the required 0 for an allowed exact advisory/date. | +| `OTEL-FUTURE-REVIEW` | Gate rejects vulnerability generally, but lacks the required invalid-date record diagnostic. | +| `MCP-EXACT-CVE` | Actual audit gate returns 1 instead of the required 0 for an allowed exact advisory/date. | +| `MCP-EXACT-GHSA` | Actual audit gate returns 1 instead of the required 0 for an allowed exact advisory/date. | +| `MCP-EXPIRED` | Gate rejects vulnerability generally, but lacks the required invalid-date record diagnostic. | +| `MCP-EXPIRY-DAY` | Actual audit gate returns 1 instead of the required 0 for an allowed exact advisory/date. | +| `MCP-FUTURE-REVIEW` | Gate rejects vulnerability generally, but lacks the required invalid-date record diagnostic. | +| `MCP-OFFICIAL-EXCEPTION-EXACT` | Frozen export has semgrep==1.175.0 instead of semgrep==1.179.0. | +| `CERTIFI-COMPROMISED-ROOT-ABSENT` | Downloaded hash-verified frozen certifi contains prohibited root fingerprint 86a1ecba...a36bbe6b. | +| `PYJWT-OPTIONS-AND-EXPIRY` | Actual old wheel mutates caller options, adding seven verification fields. | +| `RELEASE-0-55-11-VERSION-SOURCES` | Current project version is 0.55.4 instead of 0.55.11. | +| `SOURCE-DISTRIBUTION-BUNDLED-MANIFEST` | Backend reaches wheel-from-sdist and cannot find forced include modules/bundle-mapper. | +| `GITPYTHON-CREATE-ABSOLUTE-METADATA` | Old wheel reaches mocked clone once and reports rejected=False, boundary_reached=True. | +| `GITPYTHON-CREATE-GIT-METADATA` | Old wheel reaches mocked clone once and reports rejected=False, boundary_reached=True. | +| `GITPYTHON-CREATE-GIT-SHORT-NAME` | Old wheel reaches mocked clone once and reports rejected=False, boundary_reached=True. | +| `GITPYTHON-SUBMODULE-CONTAINMENT` | Old wheel reaches the explicit clone boundary sentinel after outside-checkout lookup. | +| `CERTIFI-FROZEN-SELECTION` | Frozen version differs from the exact required reviewed version. | +| `GITPYTHON-FROZEN-SELECTION` | Frozen version differs from the exact required reviewed version. | +| `HATCHLING-FROZEN-SELECTION` | Frozen version differs from the exact required reviewed version. | +| `PACKAGING-FROZEN-SELECTION` | Frozen version differs from the exact required reviewed version. | +| `PYCPARSER-FROZEN-SELECTION` | Committed record or frozen selection is 2.22; required exact version is 3.12. | +| `PYJWT-FROZEN-SELECTION` | Frozen version differs from the exact required reviewed version. | +| `PYTHON-DISCOVERY-FROZEN-SELECTION` | Frozen version differs from the exact required reviewed version. | +| `URLLIB3-FROZEN-SELECTION` | Frozen version differs from the exact required reviewed version. | +| `VIRTUALENV-FROZEN-SELECTION` | Frozen version differs from the exact required reviewed version. | +| `JEKYLL-FEED-LOCKED` | Actual Gemfile.lock lacks the required exact plugin version. | +| `JEKYLL-REDIRECT-FROM-LOCKED` | Actual Gemfile.lock lacks the required exact plugin version. | +| `JEKYLL-RELATIVE-LINKS-LOCKED` | Actual Gemfile.lock lacks the required exact plugin version. | +| `JSON-REVIEWED-FLOOR` | Gemfile has >=2.19.9 and lacks required >=2.21.2 with <3 bound. | + +For the source distribution, the trace explicitly reaches `hatchling.build.build_wheel`, forced-inclusion traversal, and `FileNotFoundError: Forced include not found .../specfact_cli-0.55.4/modules/bundle-mapper`. This is the packaging behavior under test, not a missing tool or collection failure. For submodule update, the trace traverses the old GitPython outside checkout lookup and reaches the deliberately mocked `_clone_repo` sentinel with `AssertionError: clone boundary reached`. For creation, all three prohibited paths report one clone call, `rejected=False`, and `boundary_reached=True`. The ordinary and `.github` positive controls pass, showing the mock boundary can be reached for valid paths. The certifi test reads the actual hash-pinned wheel trust store and observes the prohibited fingerprint. The JWT test installs the pinned old delivery package in an isolated target and observes actual options mutation. No setup exception is counted as behavioral proof. + +The smoke fixture changes only two environment removals, `PYTHONPATH` and `PYTHONHOME`, before the existing bootstrap. All three real bootstrap commands, real CLI invocation, CLI success marker, and parent package inventory comparison remain. I inspected the actual child probe source and before/after JSON. Before the edit, the sentinel module imports through inherited PYTHONPATH and the owner-default src path is visible; both child cases inherit PYTHONHOME. After the edit, both variables are absent, neither sentinel nor parent src is visible, and both children exit 0. This verifies environment construction with real child Python. It does not claim that the complete bootstrap or CLI smoke ran successfully for the current correction. + +I read the managed official-source investigation at `/Users/dom/.codex/state/plugins/codex-security/scans/security-dependencies-0-55-11/artifacts-911581b64ba03e8c6a2c66516d91e6dc5c452f0bbad92e640370aae36e201715/artifacts/pycparser-supported-replacement-investigation.md`, SHA-256 `3421322320d59bf16d339ecb6bc0f6ffdc5f02bd20ade001d2290be7cd1faacc`. It records fresh official PyPI, immutable GitHub and exact-wheel Socket observations accessed on 2026-10-23 Europe/Berlin. I independently recomputed the five retained download hashes, verified all eight wheel package files against both the official sdist and immutable upstream archive at `3ec766b7cd94724cba714296509604597ea4ce4e`, checked wheel RECORD digests and lengths, and verified the identical packaged/upstream/sdist BSD license SHA-256 `0a9e78156ae8b96caf1b4f11ac2df424b83de29736f9e757630548a6272fdf90`. The retained wheel SHA-256 equals the exact contract `a076f3f489b39b43ed13563a1dafe42447445bec2e294154ec5c57fcdda4c85e`. + +That evidence supports factual source-provenance review. It discloses optional preprocessing, filesystem access, URL strings, and the license-heading classification. It does not support an alert-free claim, exhaustive parser safety claim, all-platform compatibility claim, or organization-specific Socket PR approval. I did not rerun the investigator's installs or compatibility probes and do not claim to have done so. + +The proposal and TDD companion clarification correctly limit prior Semgrep 1.179.0 runtime probes and schema 1.2 review to pinned fixture 0.48.15 with ordinary PATH passes. They distinguish the published main 0.51.2 and dev 0.51.4 capsule path, which clears the environment, uses `-I -S`, dedicated PATH and capsule version maps, and returns UNKNOWN on failure without host fallback. I inspected the retained source trace. The text explicitly states that signature verification and capsule execution did not run. It makes no false signature or capsule-runtime assurance claim. + +The actual initial whole PR Code Review had three DRY warnings and six infos. The warnings were real review findings and triggered the table repair. The initial report remains intact. The final post-comment actual report is schema 1.2 run `review-604c5557-2339-4f01-b564-0e3a6566beb7`, timestamp `2026-10-22T23:22:39.529594Z`, `PASS_WITH_ADVISORY`, exit 0, zero errors, zero warnings and six infos. Its SHA-256 is `ba003b07c462f10a2fefcffeaa1ed41c5e201275ee526bcee855747a2d9135b3`, byte-identical to actual `.specfact/code-review.json` at inspection. The actual argv covers all 13 PR Python paths. No machine report was normalized, and the six findings remain visible. + +My fresh source-specific dispositions preserve those six informational design judgments: + +| Report location | Disposition and source reason | +| --- | --- | +| `tests/integration/scripts/test_runtime_discovery_smoke.py:58` | Preserve. The 56-line test owns disposable environment construction, three bootstrap commands, actual CLI execution and unchanged parent inventory. Moving these into a helper would hide the tested isolation boundary. The two added removals have actual child-interpreter evidence. | +| `tests/unit/docs/test_docs_validation_scripts.py:189` | Preserve. The 45-line test's frontmatter and navigation YAML are literal fixture content for route resolution. Compressing or extracting those literals would separate the tested document from its assertion. | +| `tests/unit/security/test_frozen_jwt_compatibility.py:37` | Preserve. The 48-line test keeps exact export parsing, pinned isolated install, child JSON observation and separate options, expiry, signed and padded-token outcomes together. A comprehension would not replace these distinct steps. | +| `tests/unit/security/test_release_promotion_security_gates.py:244` | Preserve. The 44-line function checks trusted materialization, forbidden artifact reuse, trusted imports, consumer execution order and timeout. They are distinct integrity invariants with explicit literal workflow inputs. | +| `tests/unit/security/test_release_promotion_security_gates.py:393` | Preserve. The 53-line function asserts exact official graph and two separate exact expiring advisory records, including mitigation/rationale presence. The record literals are the reviewed domain contract. | +| `tests/unit/security/test_release_promotion_security_gates.py:545` | Preserve. The 43-line test supplies the full external authority CLI boundary and asserts fail-closed behavior for invalid UTF-8. Extracting that argv hides which inputs the malformed-byte invocation actually exercises. | + +The final source hashes for all 13 reviewed Python paths are in `reviewer/final-code-review-source-binding.json`. The selected test and frozen boundary hashes are also in `reviewer/local-evidence-verification.json`. Earlier `reviewer/code-review-source-binding.json` remains historical, before removal of the two docstrings. + +All twelve restored production paths still match `restoration.json`, including absent new Ruby probe and workflow paths. Trust register SHA-256 remains `aed15e900850691789baedbcd7ee6c82cef6a80e8e09bf95ab9ac8f23329a875`; checker SHA-256 remains `4433fabc6412476764da49548e44643def974ae482bf27e78ec2ed211d15970c`. Held docs-floor file SHA-256 remains `29aae452c99fa57f49df3758951f294a8cdfbfb22c5df1b77631a8ea003a9c98`. At initial inspection all 2404 nonowned tracked paths matched the preparation baseline. The bounded table amendment changed only the trust test. The Git index remains empty at final acceptance. + +The unchanged real-clock refusal is actual retained evidence in `advisory-green/dependency-trust.log` and `smart-test-full.xml`. It reports pycparser 2.22 expired on 2026-10-22, including the actual `-S` child exiting 1. I verified the restored register/checker identities and the unchanged real-clock test function. I do not relabel that earlier full suite as current or passing. No clock was frozen to bypass expiry. The later changed production state must pass the real-date gate again. + +At this acceptance, active status prose and receipt still truthfully say pending. Root may now reconcile those operational records with this verdict and actual local metrics. This report binds the listed proposal, design, spec, mapping and final tests, rather than requiring the pending-status files never to change. Root must rerun canonical planning after adding the repository-relative review and receipt, confirm identical canonical identities and immutable test/spec hashes, and retain the old receipts as history. The authentic 31-case native proof cannot approve the new test bytes. + +Confidence is high for mapping identity, preserved control semantics, source correspondence, actual local failure causes and the bounded review dispositions. It is limited for platform/runtime behavior that has not run. Artifact-source mismatch would invalidate evidence, so root must keep these exact hashes through test-only commit and native proof. Date expiry can affect UTC CI independently of Berlin observations, so actual-date gates stay mandatory. Earlier proof can be misapplied after a byte change, so fresh native RED and then production GREEN must bind the new commit and complete test/import context. + +This review used temporary artifact writes and existing local Python only. No release or paid-service side effect occurred. Rollback is restoring the saved bounded test/spec diff; root owns that operation. The internal wiki authorization/update remains a separate recorded follow-up. Full smoke, frozen graph/export refresh, parser dependency floors in both declarations, new exact provenance record, audits, signatures, all required local/native gates and final independent production review remain open. + +Test Behavior, Not Implementation shaped the decision to require direct artifact and child-process outcomes. Prove It Works shaped independent canonical planning, hash recomputation, literal-row equivalence checks and inspection of actual XML failure traces. These principles were read in this review session. + +Next action is root's status/receipt reconciliation, normal test-only hooks and commit, followed by authentication of a fresh native 33-case RED artifact. This acceptance does not open production adoption before that native gate. diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-trust-review-evidence.json b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-trust-review-evidence.json new file mode 100644 index 00000000..15979946 --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/pre-trust-review-evidence.json @@ -0,0 +1,9 @@ +{ + "schema_version": "1", + "decision": "accepted", + "reviewer_id": "Codex:/root/quality_mapping_acceptance (gpt-6.1-sol)", + "reviewer_role": "independent AI technical mapping reviewer", + "recorded_at": "2026-10-22T22:39:57.741951+00:00", + "reference": "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/quality-mapping-review.md", + "mapping_digest": "sha256:0788c6326b39842e80a8cf4e308ac8508c754c384d9e6479b5cd61f0ca8b1c1d" +} diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json index 15979946..1e559b82 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/review-evidence.json @@ -1,9 +1,9 @@ { "schema_version": "1", "decision": "accepted", - "reviewer_id": "Codex:/root/quality_mapping_acceptance (gpt-6.1-sol)", + "reviewer_id": "Codex:/root/parser_mapping_acceptance (gpt-6.1-sol)", "reviewer_role": "independent AI technical mapping reviewer", - "recorded_at": "2026-10-22T22:39:57.741951+00:00", - "reference": "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/quality-mapping-review.md", - "mapping_digest": "sha256:0788c6326b39842e80a8cf4e308ac8508c754c384d9e6479b5cd61f0ca8b1c1d" + "recorded_at": "2026-10-22T23:27:31.405035+00:00", + "reference": "openspec/changes/isolate-semgrep-security-runtime/requirements-proof/parser-mapping-review.md", + "mapping_digest": "sha256:b192853e22bfc854a30366891d76942d5c2ba9cc47260197c3c4df7bd77c923c" } diff --git a/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/trust-correction-review-pending.md b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/trust-correction-review-pending.md new file mode 100644 index 00000000..59eb065e --- /dev/null +++ b/openspec/changes/isolate-semgrep-security-runtime/requirements-proof/trust-correction-review-pending.md @@ -0,0 +1,20 @@ +# Supported parser correction review pending + +This historical preparation note is superseded by `parser-mapping-review.md` +and the active accepted receipt. The final 33-case mapping has independent AI +acceptance and local RED with 33 passing controls. Fresh native RED is pending. +The authentic quality amendment acceptance is retained in +`pre-trust-review-evidence.json` and `quality-mapping-review.md`. Its 31-case +native RED at commit 4936750186fcb339913a8710f3034cf1d2be901f, run 39866724812 +attempt 1, remains valid only for its original source and test bytes. + +This correction adds exact pycparser 3.12 lock/export selection and an exact +bounded provenance record. All original 31 selectors and supplementary controls +remain. The wrong-hash and wrong-package fixtures use the reviewed version and +date so they continue to measure artifact identity. The real-clock gate, expiry, +3.0 aliases and prohibited-wheel rejection remain enforced. + +The smoke fixture removes inherited PYTHONPATH and PYTHONHOME after a genuine +child-Python probe reproduced both leaks. Its full bootstrap and actual CLI smoke +still require later execution. Production paths and the trust register/checker +remain frozen. This status grants no acceptance, release or merge authority. diff --git a/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md b/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md index c2575f14..ce7d0a76 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md +++ b/openspec/changes/isolate-semgrep-security-runtime/specs/isolated-scanner-runtime/spec.md @@ -36,7 +36,7 @@ The main release SHALL satisfy this delivery contract using the reviewed officia #### Scenario: Select the reviewed compatible tooling graph - **WHEN** the release frozen graph is resolved -- **THEN** it SHALL select certifi 2026.10.19, GitPython 3.2.1, PyJWT 2.15.1, Hatchling 1.32.4, urllib3 2.8.0, virtualenv 21.14.12, python-discovery 1.6.1, and packaging 26.3 +- **THEN** it SHALL select certifi 2026.10.19, GitPython 3.2.1, PyJWT 2.15.1, Hatchling 1.32.4, urllib3 2.8.0, virtualenv 21.14.12, python-discovery 1.6.1, packaging 26.3, and pycparser 3.12 - **AND** the lock and hashed delivery export SHALL agree. ### Requirement: Bound the temporary official MCP advisory exception @@ -100,3 +100,17 @@ accept untrusted propagation headers. - **THEN** only CVE-2026-114603 and alias GHSA-xcq7-p3pf-p88h SHALL be waived from 2026-10-22 through 2026-11-04 inclusive - **AND** other packages or versions, unrelated or mixed advisories, skipped dependencies, missing entries, expired records, and future review dates SHALL fail closed - **AND** the separate MCP record SHALL preserve its original version, advisory IDs, review date, and expiry. + +### Requirement: Bind the supported parser to current source provenance + +The main release SHALL replace expired pycparser 2.22 evidence with a new exact +pycparser 3.12 source-provenance review. Both dependency declarations SHALL require +`pycparser>=3.12,!=3.0.*`. The 3.0 release-family block SHALL remain enforced. + +#### Scenario: Bind the supported parser to current source provenance + +- **WHEN** the required parser's review record is read +- **THEN** it SHALL bind pycparser 3.12 to the official wheel URL `https://files.pythonhosted.org/packages/b6/ae/315bbeee126a769cbde43cf4908acb4b40408d8bf8bbf334dd7368d8dadf/pycparser-3.12-py3-none-any.whl` and SHA-256 `a076f3f489b39b43ed13563a1dafe42447445bec2e294154ec5c57fcdda4c85e` +- **AND** its classification SHALL be `source-provenance-reviewed`, with review date 2026-10-22 UTC and expiry 2026-11-04 inclusive +- **AND** its transitive path SHALL retain cryptography, CFFI, and pycparser +- **AND** actual-date expiry, wrong-artifact and wrong-package rejection, blocked 3.0 aliases, and prohibited executable-wheel controls SHALL remain enforced. diff --git a/openspec/changes/isolate-semgrep-security-runtime/tasks.md b/openspec/changes/isolate-semgrep-security-runtime/tasks.md index 9e71083b..5268772c 100644 --- a/openspec/changes/isolate-semgrep-security-runtime/tasks.md +++ b/openspec/changes/isolate-semgrep-security-runtime/tasks.md @@ -5,7 +5,7 @@ - [x] Retain the accepted 23-case review with a repository-relative reference. - [x] Commit the 23-case test-authored content with local hooks and retain native failing CI evidence. - [ ] Freeze mappings, selected tests, ancestor conftest files, and transitive Python imports. -- [ ] Refresh the nine reviewed official Python packages and both frozen files. +- [ ] Refresh the ten reviewed official Python packages and both frozen files. - [ ] Add the two separate exact approved MCP and OpenTelemetry advisory exceptions after new native RED. - [ ] Update documentation dependency declarations and lock, then execute real gem compatibility probes. - [ ] After native RED acceptance, port the reviewed dev Ruby compatibility probe to `scripts/check_jekyll_dependencies.rb` and its CI workflow to `.github/workflows/docs-dependency-checks.yml`. @@ -35,7 +35,7 @@ See the appended corrected-production section in TDD_EVIDENCE.md for actual resu - [x] Run released canonical planning against exact base 5262efb0881e4fac7697d629b0530250342bc656. - [x] Inspect genuine local failures for every selected case and pass every supplementary control. - [x] Obtain fresh independent mapping acceptance in requirements-proof/advisory-mapping-review.md. -- [ ] Authenticate new native RED before production adoption. +- [x] Authenticate new native RED before production adoption. - [ ] Reapply only the approved production correction and complete release verification. The earlier 23-case acceptance and native RED are complete historical evidence. @@ -49,9 +49,31 @@ they are reconciled here and in CHANGE_VALIDATION.md and TDD_EVIDENCE.md. - [x] Retain six visible informational findings with independently accepted source inspections. - [x] Refresh the exact 31 local RED cases and pass all 23 supplementary controls. - [x] Record fresh independent acceptance in requirements-proof/quality-mapping-review.md. -- [ ] Commit the test-only amendment with normal hooks, holding the docs floor for its matching metadata. -- [ ] Authenticate fresh native RED for these exact test bytes before production adoption. +- [x] Commit the test-only amendment with normal hooks, holding the docs floor for its matching metadata. +- [x] Authenticate fresh native RED for these exact test bytes before production adoption. -The earlier advisory acceptance remains authentic history. The active receipt now -references the fresh quality amendment acceptance. No native proof is reused for -changed test bytes. Only the docs-floor fixture hunk remains held for production. +The earlier advisory acceptance remains authentic history. At this quality +checkpoint, the receipt referenced the fresh quality amendment acceptance. No native proof is reused for +changed test bytes. The docs-floor fixture hunk remains held for production. This quality proof is +historical after the parser correction below changes mapped test bytes. + +## Current supported parser correction on 2026-10-23 Europe/Berlin + +- [x] Preserve the twelve restored production files, trust policy/register, signatures, toolchain, and held docs-floor hunk. +- [x] Specify official pycparser 3.12 and its new exact provenance review through 2026-11-04 inclusive. +- [x] Add exact parser selection and provenance cases while retaining all 31 prior selectors and controls. +- [x] Remove only inherited PYTHONPATH and PYTHONHOME from the smoke fixture after actual child-Python reproduction. +- [x] Reconcile historical advisory/quality acceptance and authenticated 31-case native RED with the current pending correction. +- [x] Obtain fresh independent acceptance for the changed 33-case mapping and test bytes. +- [x] Retain genuine local RED and all 33 supplementary control results. +- [ ] Authenticate new native RED for the final source bytes before production. +- [ ] Refresh the ten official updates and both frozen files, raise the parser floors, and write the exact new provenance review. +- [ ] Obtain the pending sibling-repository authorization, update the matching internal wiki, and rebuild its graph. +- [ ] Run the actual full smoke, required release gates, independent final review, and native GREEN. + +The 2026-10-22 UTC source review occurred on 2026-10-23 Europe/Berlin. The date +basis is recorded explicitly; the real clock and trust checker are unchanged. +Initial preparation ran no mapped pytest. Fresh independent acceptance then +preceded final local execution with 33 expected failures and 33 passing controls. +Production adoption, native RED, signing, commit and push are separate later steps. Native proof for the prior 31 cases cannot approve +changed test bytes. The earlier full-suite and native results remain historical. diff --git a/tests/integration/scripts/test_runtime_discovery_smoke.py b/tests/integration/scripts/test_runtime_discovery_smoke.py index 66e256ba..b89ce1ae 100644 --- a/tests/integration/scripts/test_runtime_discovery_smoke.py +++ b/tests/integration/scripts/test_runtime_discovery_smoke.py @@ -65,6 +65,8 @@ def test_runtime_discovery_smoke_direct_launcher(tmp_path: Path) -> None: bin_dir = venv / ("Scripts" if os.name == "nt" else "bin") python = bin_dir / ("python.exe" if os.name == "nt" else "python") env = os.environ.copy() + env.pop("PYTHONPATH", None) + env.pop("PYTHONHOME", None) env.update( VIRTUAL_ENV=str(venv), HATCH_ENV_TYPE_VIRTUAL_PATH=str(venv), diff --git a/tests/unit/scripts/test_dependency_trust_review.py b/tests/unit/scripts/test_dependency_trust_review.py index 95987975..76e77e78 100644 --- a/tests/unit/scripts/test_dependency_trust_review.py +++ b/tests/unit/scripts/test_dependency_trust_review.py @@ -8,6 +8,9 @@ import sys from datetime import date from pathlib import Path +from types import ModuleType + +import pytest REPO_ROOT = Path(__file__).resolve().parents[3] @@ -15,7 +18,7 @@ CHECKER = REPO_ROOT / "scripts" / "check_dependency_trust_exceptions.py" -def _load_checker(): +def _load_checker() -> ModuleType: spec = importlib.util.spec_from_file_location("check_dependency_trust_exceptions", CHECKER) assert spec is not None and spec.loader is not None module = importlib.util.module_from_spec(spec) @@ -43,19 +46,22 @@ def test_pycparser_has_current_exact_version_review_record() -> None: payload = json.loads(TRUST_RECORD.read_text(encoding="utf-8")) records = payload["exceptions"] pycparser = next(record for record in records if record["package"] == "pycparser") - assert pycparser["version"] == "2.22" - assert pycparser["source_url"].startswith("https://files.pythonhosted.org/") - assert pycparser["artifact_sha256"] == "c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" + assert pycparser["version"] == "3.12" + assert pycparser["source_url"] == ( + "https://files.pythonhosted.org/packages/b6/ae/315bbeee126a769cbde43cf4908acb4b40408d8bf8bbf334dd7368d8dadf/" + "pycparser-3.12-py3-none-any.whl" + ) + assert pycparser["artifact_sha256"] == "a076f3f489b39b43ed13563a1dafe42447445bec2e294154ec5c57fcdda4c85e" assert pycparser["classification"] == "source-provenance-reviewed" - assert pycparser["expires_on"] >= "2026-07-24" - assert "cryptography" in pycparser["transitive_path"] + assert pycparser["reviewed_on"] == "2026-10-22" + assert pycparser["expires_on"] == "2026-11-04" + assert pycparser["transitive_path"] == "specfact-cli -> cryptography -> cffi -> pycparser" -def test_alerted_pycparser_release_is_blocked_even_with_a_review_record(tmp_path: Path) -> None: - """A Socket-alerted release cannot be normalized through an exception record.""" - record_path = tmp_path / "exceptions.json" - record_path.write_text( - json.dumps( +@pytest.mark.parametrize( + "payload, today, expected_errors", + [ + pytest.param( { "exceptions": [ { @@ -70,15 +76,63 @@ def test_alerted_pycparser_release_is_blocked_even_with_a_review_record(tmp_path "rationale": "test fixture", } ] - } + }, + date(2026, 7, 24), + ["pycparser==3.0 is blocked after a security-obfuscation alert"], + id="blocked-pycparser-3.0", ), - encoding="utf-8", - ) + pytest.param( + { + "exceptions": [ + { + "package": "pycparser", + "version": "2.22", + "source_url": "https://files.pythonhosted.org/example.whl", + "artifact_sha256": "c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", + "classification": "source-provenance-reviewed", + "reviewed_on": "2026-07-24", + "expires_on": "2026-07-23", + "transitive_path": "specfact-cli -> cryptography -> cffi -> pycparser", + "rationale": "test fixture", + } + ] + }, + date(2026, 7, 24), + ["pycparser==2.22 expired on 2026-07-23"], + id="expired-review-record", + ), + pytest.param( + { + "exceptions": [ + { + "package": "NodeJS_Wheel.Binaries", + "version": "1.0.0", + "source_url": "https://files.pythonhosted.org/example.whl", + "artifact_sha256": "c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", + "classification": "source-provenance-reviewed", + "reviewed_on": "2026-07-23", + "expires_on": "2026-10-22", + "transitive_path": "specfact-cli -> test fixture", + "rationale": "test fixture", + } + ] + }, + date(2026, 7, 24), + ["nodejs-wheel-binaries==1.0.0 is prohibited from the dependency trust register"], + id="prohibited-pep503-name", + ), + ], +) +def test_invalid_trust_register_is_rejected( + tmp_path: Path, payload: dict[str, list[dict[str, str]]], today: date, expected_errors: list[str] +) -> None: + record_path = tmp_path / "exceptions.json" + record_path.write_text(json.dumps(payload), encoding="utf-8") checker = _load_checker() - errors = checker.validate_exception_register(record_path, today=date(2026, 7, 24)) + errors = checker.validate_exception_register(record_path, today=today) - assert errors == ["pycparser==3.0 is blocked after a security-obfuscation alert"] + assert errors == expected_errors def test_alerted_pycparser_release_family_is_blocked_with_a_pep440_spelling(tmp_path: Path) -> None: @@ -109,47 +163,18 @@ def test_unofficial_executable_wheel_is_not_accepted() -> None: assert all(record["package"] != "nodejs-wheel-binaries" for record in payload["exceptions"]) -def test_expired_exception_fails_closed(tmp_path: Path) -> None: - """Expiry is enforced by CI rather than merely documented in static policy.""" - record_path = tmp_path / "exceptions.json" - record_path.write_text( - json.dumps( - { - "exceptions": [ - { - "package": "pycparser", - "version": "2.22", - "source_url": "https://files.pythonhosted.org/example.whl", - "artifact_sha256": "c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", - "classification": "source-provenance-reviewed", - "reviewed_on": "2026-07-24", - "expires_on": "2026-07-23", - "transitive_path": "specfact-cli -> cryptography -> cffi -> pycparser", - "rationale": "test fixture", - } - ] - } - ), - encoding="utf-8", - ) - checker = _load_checker() - - errors = checker.validate_exception_register(record_path, today=date(2026, 7, 24)) - - assert errors == ["pycparser==2.22 expired on 2026-07-23"] - - def test_reviewed_artifact_must_exist_in_the_frozen_lock(tmp_path: Path) -> None: """A copied review record cannot approve a different frozen artifact.""" payload = json.loads(TRUST_RECORD.read_text(encoding="utf-8")) - payload["exceptions"][0]["artifact_sha256"] = "0" * 64 + reviewed = payload["exceptions"][0] + reviewed["artifact_sha256"] = "0" * 64 register_path = tmp_path / "exceptions.json" register_path.write_text(json.dumps(payload), encoding="utf-8") checker = _load_checker() - errors = checker.validate_frozen_dependency_policy(register_path, today=date(2026, 7, 24)) + errors = checker.validate_frozen_dependency_policy(register_path, today=date.fromisoformat(reviewed["reviewed_on"])) - assert errors == ["pycparser==2.22 artifact digest is absent from its frozen lock record"] + assert errors == [f"pycparser=={reviewed['version']} artifact digest is absent from its frozen lock record"] def test_review_evidence_must_bind_to_the_matching_lock_package(tmp_path: Path) -> None: @@ -164,7 +189,7 @@ def test_review_evidence_must_bind_to_the_matching_lock_package(tmp_path: Path) [ "[[package]]", 'name = "pycparser"', - 'version = "2.22"', + f'version = "{reviewed["version"]}"', 'sdist = { url = "https://files.pythonhosted.org/other.tar.gz", hash = "sha256:' + "0" * 64 + '" }', "", "[[package]]", @@ -182,68 +207,42 @@ def test_review_evidence_must_bind_to_the_matching_lock_package(tmp_path: Path) ) checker = _load_checker() - errors = checker.validate_frozen_dependency_policy(register_path, lock_path, today=date(2026, 7, 24)) + errors = checker.validate_frozen_dependency_policy( + register_path, lock_path, today=date.fromisoformat(reviewed["reviewed_on"]) + ) assert errors == [ - "pycparser==2.22 source artifact is absent from its frozen lock record", - "pycparser==2.22 artifact digest is absent from its frozen lock record", + f"pycparser=={reviewed['version']} source artifact is absent from its frozen lock record", + f"pycparser=={reviewed['version']} artifact digest is absent from its frozen lock record", ] -def test_prohibited_executable_wheel_name_is_pep503_normalized(tmp_path: Path) -> None: - """Separator and case variants cannot evade the executable-wheel denylist.""" - record_path = tmp_path / "exceptions.json" - record_path.write_text( - json.dumps( - { - "exceptions": [ - { - "package": "NodeJS_Wheel.Binaries", - "version": "1.0.0", - "source_url": "https://files.pythonhosted.org/example.whl", - "artifact_sha256": "c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", - "classification": "source-provenance-reviewed", - "reviewed_on": "2026-07-23", - "expires_on": "2026-10-22", - "transitive_path": "specfact-cli -> test fixture", - "rationale": "test fixture", - } - ] - } +@pytest.mark.parametrize( + "lock_text, today, expected_errors", + [ + pytest.param( + '\n[[package]]\nname = "NodeJS_Wheel.Binaries"\nversion = "1.0.0"\n', + date(2026, 7, 24), + ["nodejs-wheel-binaries==1.0.0 is prohibited in the frozen lock"], + id="prohibited-executable-wheel", ), - encoding="utf-8", - ) - checker = _load_checker() - - errors = checker.validate_exception_register(record_path, today=date(2026, 7, 24)) - - assert errors == ["nodejs-wheel-binaries==1.0.0 is prohibited from the dependency trust register"] - - -def test_prohibited_executable_wheel_in_lock_fails_without_an_exception(tmp_path: Path) -> None: - """The lock itself is the enforcement boundary for executable wheel runtimes.""" - register_path = tmp_path / "exceptions.json" - register_path.write_text('{"exceptions": []}', encoding="utf-8") - lock_path = tmp_path / "uv.lock" - lock_path.write_text( - '\n[[package]]\nname = "NodeJS_Wheel.Binaries"\nversion = "1.0.0"\n', - encoding="utf-8", - ) - checker = _load_checker() - - errors = checker.validate_frozen_dependency_policy(register_path, lock_path, today=date(2026, 7, 24)) - - assert errors == ["nodejs-wheel-binaries==1.0.0 is prohibited in the frozen lock"] - - -def test_semgrep_lock_below_reviewed_security_floor_fails_before_install(tmp_path: Path) -> None: - """A lock downgrade cannot silently restore the reported vulnerable Semgrep line.""" + pytest.param( + '\n[[package]]\nname = "semgrep"\nversion = "1.70.1"\n', + date(2026, 7, 24), + ["semgrep==1.70.1 is below the reviewed security floor 1.175.0"], + id="semgrep-below-security-floor", + ), + ], +) +def test_invalid_frozen_lock_is_rejected( + tmp_path: Path, lock_text: str, today: date, expected_errors: list[str] +) -> None: register_path = tmp_path / "exceptions.json" register_path.write_text('{"exceptions": []}', encoding="utf-8") lock_path = tmp_path / "uv.lock" - lock_path.write_text('\n[[package]]\nname = "semgrep"\nversion = "1.70.1"\n', encoding="utf-8") + lock_path.write_text(lock_text, encoding="utf-8") checker = _load_checker() - errors = checker.validate_frozen_dependency_policy(register_path, lock_path, today=date(2026, 7, 24)) + errors = checker.validate_frozen_dependency_policy(register_path, lock_path, today=today) - assert errors == ["semgrep==1.70.1 is below the reviewed security floor 1.175.0"] + assert errors == expected_errors diff --git a/tests/unit/security/test_release_dependency_selection.py b/tests/unit/security/test_release_dependency_selection.py index 014c3351..8301f852 100644 --- a/tests/unit/security/test_release_dependency_selection.py +++ b/tests/unit/security/test_release_dependency_selection.py @@ -21,6 +21,7 @@ ("virtualenv", "21.14.12"), ("python-discovery", "1.6.1"), ("packaging", "26.3"), + ("pycparser", "3.12"), ], ) def test_reviewed_tooling_versions_match_frozen_export(name: str, version: str) -> None: