From af5516cb8f25672be6f907b4fee187281ffeb2aa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:08:52 +0900 Subject: [PATCH 01/19] test(ci): materialize legal Strix path regression --- .../repair-strix-legal-packrat-paths.yml | 290 ++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 .github/workflows/repair-strix-legal-packrat-paths.yml diff --git a/.github/workflows/repair-strix-legal-packrat-paths.yml b/.github/workflows/repair-strix-legal-packrat-paths.yml new file mode 100644 index 000000000..5ccfe5410 --- /dev/null +++ b/.github/workflows/repair-strix-legal-packrat-paths.yml @@ -0,0 +1,290 @@ +name: Materialize legal Strix changed-path repair + +on: + push: + branches: + - fix/strix-legal-packrat-paths + +permissions: + contents: read + +concurrency: + group: materialize-strix-legal-packrat-paths + cancel-in-progress: false + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/.github' && + github.ref == 'refs/heads/fix/strix-legal-packrat-paths' + runs-on: ubuntu-24.04 + timeout-minutes: 60 + permissions: + contents: write + steps: + - name: Materialize exact branch without persisted credentials + env: + EXPECTED_BASE_SHA: f070c504c1cb06891b800d7ab0cf6ac7d3cf8eae + REPAIR_WORKFLOW: .github/workflows/repair-strix-legal-packrat-paths.yml + run: | + set -euo pipefail + git init "$GITHUB_WORKSPACE" + git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" + git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=2 origin "$GITHUB_SHA" + git -C "$GITHUB_WORKSPACE" checkout --detach "$GITHUB_SHA" + test "$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD^)" = "$EXPECTED_BASE_SHA" + test "$(git -C "$GITHUB_WORKSPACE" diff --name-only HEAD^ HEAD)" = "$REPAIR_WORKFLOW" + + - name: Prove RED, implement narrow repair, and publish verified product commit + env: + GITHUB_TOKEN: ${{ github.token }} + EXPECTED_BASE_SHA: f070c504c1cb06891b800d7ab0cf6ac7d3cf8eae + REPAIR_BRANCH: fix/strix-legal-packrat-paths + REPAIR_WORKFLOW: .github/workflows/repair-strix-legal-packrat-paths.yml + working-directory: ${{ github.workspace }} + run: | + set -euo pipefail + + cat > tests/test_strix_changed_path_policy.py <<'PY' + """Regression tests for the production Strix changed-path normalizer.""" + + from __future__ import annotations + + import subprocess + import sys + import tempfile + import unittest + from pathlib import Path + + + REPOSITORY_ROOT = Path(__file__).resolve().parents[1] + GATE_SCRIPT = REPOSITORY_ROOT / "scripts" / "ci" / "strix_quick_gate.sh" + START_MARKER = 'python3 - "$REPO_ROOT" "$changed_file" <<\'PY\'\n' + END_MARKER = "\nPY\n}\n\nnormalize_changed_files_cache()" + LEGAL_PACKRAT_PATH = ( + "packrat/lib/x86_64-pc-linux-gnu/3.4.1/packrat/tests/testthat/" + "Ugly, but legal, path for a project (long)/bread/DESCRIPTION" + ) + + + def _normalizer_source() -> str: + """Return the exact embedded Python program used in production.""" + + gate_source = GATE_SCRIPT.read_text(encoding="utf-8") + prefix, separator, remainder = gate_source.partition(START_MARKER) + if not separator or not prefix: + raise AssertionError("Strix changed-path normalizer start marker is missing") + source, separator, _suffix = remainder.partition(END_MARKER) + if not separator: + raise AssertionError("Strix changed-path normalizer end marker is missing") + return source + + + def _normalize(candidate: str) -> subprocess.CompletedProcess[str]: + """Execute the production normalizer with an isolated repository root.""" + + with tempfile.TemporaryDirectory() as temporary_directory: + return subprocess.run( + [ + sys.executable, + "-c", + _normalizer_source(), + temporary_directory, + candidate, + ], + check=False, + capture_output=True, + text=True, + ) + + + class StrixChangedPathPolicyTests(unittest.TestCase): + """Verify legal Git paths and fail-closed path boundaries.""" + + def test_accepts_historical_packrat_fixture_path(self) -> None: + """A tracked Packrat fixture with commas and parentheses is valid input.""" + + result = _normalize(LEGAL_PACKRAT_PATH) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), LEGAL_PACKRAT_PATH) + + def test_preserves_existing_supported_punctuation(self) -> None: + """Existing bracket, at-sign, plus-sign, space, and hyphen support remains.""" + + candidate = "ui/[slug]/128x128@2x +page-safe/file-name.ts" + result = _normalize(candidate) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), candidate) + + def test_rejects_traversal_absolute_controls_and_shell_punctuation(self) -> None: + """The repair does not admit traversal, controls, or shell syntax.""" + + rejected = ( + "", + ".", + "..", + "../secret.txt", + "/tmp/secret.txt", + "safe\\escape.txt", + "safe\nname.txt", + "safe\rname.txt", + " leading.txt", + "trailing.txt ", + "safe;command.txt", + "safe$(command).txt", + "safe`command`.txt", + "safe|command.txt", + "safe&command.txt", + ) + for candidate in rejected: + with self.subTest(candidate=repr(candidate)): + result = _normalize(candidate) + self.assertNotEqual(result.returncode, 0) + self.assertEqual(result.stdout, "") + + + if __name__ == "__main__": + unittest.main() + PY + + set +e + python3 tests/test_strix_changed_path_policy.py >"$RUNNER_TEMP/strix-path-red.log" 2>&1 + red_status=$? + set -e + if [ "$red_status" -eq 0 ]; then + echo "ERROR: legal Packrat path regression passed before the production repair." >&2 + exit 1 + fi + grep -Fq "test_accepts_historical_packrat_fixture_path" "$RUNNER_TEMP/strix-path-red.log" + + python3 - <<'PY' + from pathlib import Path + + gate_path = Path("scripts/ci/strix_quick_gate.sh") + source = gate_path.read_text(encoding="utf-8") + old = '''# '@' is required for Apple/Tauri retina asset names (128x128@2x.png) and '+' + # for SvelteKit's mandatory route files (+page.svelte, +layout.ts). Preserve + # the existing ASCII allowlist and additionally accept only Unicode letters, + # combining marks, and numbers. This supports internationalized repository + # paths without admitting controls, separators, bidi formatting, shell + # metacharacters, or Unicode punctuation that could resemble a path boundary. + allowed_ascii = frozenset("_.@+/ []-")''' + new = '''# '@' is required for Apple/Tauri retina asset names (128x128@2x.png) and '+' + # for SvelteKit's mandatory route files (+page.svelte, +layout.ts). Commas and + # parentheses are ordinary Git filename characters used by historical Packrat + # fixtures. They remain data because every downstream filesystem and Git call + # passes the normalized path as a quoted argument rather than shell source. + # Preserve the bounded ASCII allowlist and additionally accept only Unicode + # letters, combining marks, and numbers. This supports internationalized + # repository paths without admitting controls, separators, bidi formatting, + # shell metacharacters, or Unicode punctuation resembling a path boundary. + allowed_ascii = frozenset("_.@+/ [],()-")''' + if source.count(old) != 1: + raise SystemExit("expected exactly one Strix changed-path policy block") + gate_path.write_text(source.replace(old, new), encoding="utf-8") + PY + + cat > docs/doctoring/strix-legal-git-paths.md <<'MD' + # Strix legal Git path compatibility + + ## Incident and buyer impact + + The organization-required Strix quick gate rejected the exact changed-file list + for `ContextualWisdomLab/aFIPC#160` at head + `804ea97cd83144f94c5020a9d42f2573cc8cb442`. The pull request deletes generated + Packrat artifacts, including the tracked fixture path + `Ugly, but legal, path for a project (long)`. The central gate classified that + path as unsafe solely because its comma and parentheses were absent from the + bounded ASCII allowlist. Security analysis therefore stopped before examining + the pull request, leaving a valid supply-chain cleanup without exact-head Strix + evidence. + + ## Decision + + The normalizer now admits comma and ASCII parentheses. No other punctuation is + broadened. The existing fail-closed controls remain authoritative: + + - empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, + and backslash forms are rejected; + - shell metacharacters such as semicolon, dollar sign, backtick, pipe, and + ampersand remain rejected; + - only the existing Unicode letter, combining-mark, and number categories are + accepted outside ASCII; + - `Path.resolve(strict=False)` followed by `relative_to()` proves lexical and + symlink-aware containment beneath the trusted repository root; and + - downstream Git and filesystem operations receive the normalized path as a + quoted argument, never as executable shell source. + + This is a compatibility correction, not a general relaxation to every Git + pathname byte. Git can represent a broader set of unusual pathnames, while the + privileged scanner intentionally keeps a smaller audited policy. + + ## Test-first evidence + + `tests/test_strix_changed_path_policy.py` extracts and executes the exact Python + normalizer embedded in `scripts/ci/strix_quick_gate.sh`. The materialization run + first required the historical Packrat fixture regression to fail against the + protected-main implementation, then applied the narrow allowlist change and + required the same test to pass. Permanent tests also preserve the established + punctuation contract and reject traversal, absolute paths, controls, whitespace + ambiguity, backslashes, and representative shell punctuation. + + ## Rollback and incident response + + Roll back the allowlist and its regression together only if a downstream call is + proven to evaluate normalized paths as shell source. Until that defect is fixed, + fail the Strix gate closed and retain the exact offending path, workflow run, + and commit SHA as incident evidence. Do not bypass the required security check. + + ## References + + Git Project. (2026). *Git index format*. https://git-scm.com/docs/index-format + + Git Project. (2026). *git-ls-tree documentation*. https://git-scm.com/docs/git-ls-tree + + Python Software Foundation. (2026). *pathlib—Object-oriented filesystem paths + (Python 3.14.6 documentation)*. https://docs.python.org/3.14/library/pathlib.html + MD + + python3 - <<'PY' + from pathlib import Path + + changelog_path = Path("CHANGELOG.md") + source = changelog_path.read_text(encoding="utf-8") + marker = "### Fixed\n\n" + entry = ( + "- Allowed commas and ASCII parentheses in the bounded Strix changed-file " + "path policy so legal tracked Packrat fixtures can receive exact-head " + "security analysis while traversal, controls, backslashes, whitespace " + "ambiguity, and shell punctuation remain fail-closed.\n" + ) + if source.count(marker) != 1: + raise SystemExit("expected one Unreleased Fixed marker") + changelog_path.write_text(source.replace(marker, marker + entry, 1), encoding="utf-8") + PY + + python3 tests/test_strix_changed_path_policy.py + bash -n scripts/ci/strix_quick_gate.sh + git diff --check + + mapfile -t changed_paths < <(git diff --name-only "$EXPECTED_BASE_SHA" -- | sort) + expected_paths=( + CHANGELOG.md + docs/doctoring/strix-legal-git-paths.md + scripts/ci/strix_quick_gate.sh + tests/test_strix_changed_path_policy.py + "$REPAIR_WORKFLOW" + ) + mapfile -t expected_paths < <(printf '%s\n' "${expected_paths[@]}" | sort) + test "${changed_paths[*]}" = "${expected_paths[*]}" + + rm -- "$REPAIR_WORKFLOW" + git status --short + git config user.name "CWL Autonomous Development" + git config user.email "actions@users.noreply.github.com" + git add CHANGELOG.md docs/doctoring/strix-legal-git-paths.md scripts/ci/strix_quick_gate.sh tests/test_strix_changed_path_policy.py "$REPAIR_WORKFLOW" + git commit -m "fix(strix): accept legal Packrat fixture paths" + + auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" + git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth_header" \ + push origin "HEAD:refs/heads/$REPAIR_BRANCH" From f3a86400302a7875947761c5ea783dc3f48b1ff3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:14:06 +0900 Subject: [PATCH 02/19] fix(ci): repair legal Strix path materializer --- .../repair-strix-legal-packrat-paths-v2.yml | 289 ++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 .github/workflows/repair-strix-legal-packrat-paths-v2.yml diff --git a/.github/workflows/repair-strix-legal-packrat-paths-v2.yml b/.github/workflows/repair-strix-legal-packrat-paths-v2.yml new file mode 100644 index 000000000..e2872e3e0 --- /dev/null +++ b/.github/workflows/repair-strix-legal-packrat-paths-v2.yml @@ -0,0 +1,289 @@ +name: Repair legal Strix changed-path materialization + +on: + push: + branches: + - fix/strix-legal-packrat-paths + +permissions: + contents: read + +concurrency: + group: repair-strix-legal-packrat-paths-v2 + cancel-in-progress: false + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/.github' && + github.ref == 'refs/heads/fix/strix-legal-packrat-paths' + runs-on: ubuntu-24.04 + timeout-minutes: 60 + permissions: + contents: write + env: + EXPECTED_BASE_SHA: f070c504c1cb06891b800d7ab0cf6ac7d3cf8eae + REPAIR_BRANCH: fix/strix-legal-packrat-paths + ORIGINAL_WORKFLOW: .github/workflows/repair-strix-legal-packrat-paths.yml + REPAIR_WORKFLOW: .github/workflows/repair-strix-legal-packrat-paths-v2.yml + steps: + - name: Materialize exact repair branch without persisted credentials + run: | + set -euo pipefail + git init "$GITHUB_WORKSPACE" + git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" + git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=3 origin "$GITHUB_SHA" + git -C "$GITHUB_WORKSPACE" checkout --detach "$GITHUB_SHA" + git -C "$GITHUB_WORKSPACE" merge-base --is-ancestor "$EXPECTED_BASE_SHA" HEAD + mapfile -t committed_paths < <(git -C "$GITHUB_WORKSPACE" diff --name-only "$EXPECTED_BASE_SHA" HEAD | sort -u) + expected_paths=("$ORIGINAL_WORKFLOW" "$REPAIR_WORKFLOW") + mapfile -t expected_paths < <(printf '%s\n' "${expected_paths[@]}" | sort -u) + test "${committed_paths[*]}" = "${expected_paths[*]}" + + - name: Prove RED and publish the verified product commit + env: + GITHUB_TOKEN: ${{ github.token }} + working-directory: ${{ github.workspace }} + run: | + set -euo pipefail + + cat > tests/test_strix_changed_path_policy.py <<'PY' + """Regression tests for the production Strix changed-path normalizer.""" + + from __future__ import annotations + + import subprocess + import sys + import tempfile + import unittest + from pathlib import Path + + + REPOSITORY_ROOT = Path(__file__).resolve().parents[1] + GATE_SCRIPT = REPOSITORY_ROOT / "scripts" / "ci" / "strix_quick_gate.sh" + START_MARKER = 'python3 - "$REPO_ROOT" "$changed_file" <<\'PY\'\n' + END_MARKER = "\nPY\n}\n\nnormalize_changed_files_cache()" + LEGAL_PACKRAT_PATH = ( + "packrat/lib/x86_64-pc-linux-gnu/3.4.1/packrat/tests/testthat/" + "Ugly, but legal, path for a project (long)/bread/DESCRIPTION" + ) + + + def _normalizer_source() -> str: + """Return the exact embedded Python program used in production.""" + + gate_source = GATE_SCRIPT.read_text(encoding="utf-8") + prefix, separator, remainder = gate_source.partition(START_MARKER) + if not separator or not prefix: + raise AssertionError("Strix changed-path normalizer start marker is missing") + source, separator, _suffix = remainder.partition(END_MARKER) + if not separator: + raise AssertionError("Strix changed-path normalizer end marker is missing") + return source + + + def _normalize(candidate: str) -> subprocess.CompletedProcess[str]: + """Execute the production normalizer with an isolated repository root.""" + + with tempfile.TemporaryDirectory() as temporary_directory: + return subprocess.run( + [sys.executable, "-c", _normalizer_source(), temporary_directory, candidate], + check=False, + capture_output=True, + text=True, + ) + + + class StrixChangedPathPolicyTests(unittest.TestCase): + """Verify legal Git paths and fail-closed path boundaries.""" + + def test_accepts_historical_packrat_fixture_path(self) -> None: + """A tracked Packrat fixture with commas and parentheses is valid input.""" + + result = _normalize(LEGAL_PACKRAT_PATH) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), LEGAL_PACKRAT_PATH) + + def test_preserves_existing_supported_punctuation(self) -> None: + """Existing bracket, at-sign, plus-sign, space, and hyphen support remains.""" + + candidate = "ui/[slug]/128x128@2x +page-safe/file-name.ts" + result = _normalize(candidate) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), candidate) + + def test_rejects_traversal_absolute_controls_and_shell_punctuation(self) -> None: + """The repair does not admit traversal, controls, or shell syntax.""" + + rejected = ( + "", + ".", + "..", + "../secret.txt", + "/tmp/secret.txt", + "safe\\escape.txt", + "safe\nname.txt", + "safe\rname.txt", + " leading.txt", + "trailing.txt ", + "safe;command.txt", + "safe$(command).txt", + "safe`command`.txt", + "safe|command.txt", + "safe&command.txt", + ) + for candidate in rejected: + with self.subTest(candidate=repr(candidate)): + result = _normalize(candidate) + self.assertNotEqual(result.returncode, 0) + self.assertEqual(result.stdout, "") + + + if __name__ == "__main__": + unittest.main() + PY + + set +e + python3 tests/test_strix_changed_path_policy.py >"$RUNNER_TEMP/strix-path-red.log" 2>&1 + red_status=$? + set -e + test "$red_status" -ne 0 + grep -Fq "test_accepts_historical_packrat_fixture_path" "$RUNNER_TEMP/strix-path-red.log" + + python3 - <<'PY' + from pathlib import Path + + gate_path = Path("scripts/ci/strix_quick_gate.sh") + source = gate_path.read_text(encoding="utf-8") + old = '''# '@' is required for Apple/Tauri retina asset names (128x128@2x.png) and '+' + # for SvelteKit's mandatory route files (+page.svelte, +layout.ts). Preserve + # the existing ASCII allowlist and additionally accept only Unicode letters, + # combining marks, and numbers. This supports internationalized repository + # paths without admitting controls, separators, bidi formatting, shell + # metacharacters, or Unicode punctuation that could resemble a path boundary. + allowed_ascii = frozenset("_.@+/ []-")''' + new = '''# '@' is required for Apple/Tauri retina asset names (128x128@2x.png) and '+' + # for SvelteKit's mandatory route files (+page.svelte, +layout.ts). Commas and + # parentheses are ordinary Git filename characters used by historical Packrat + # fixtures. They remain data because downstream filesystem and Git calls pass + # the normalized path as a quoted argument rather than executable shell source. + # Preserve the bounded ASCII allowlist and additionally accept only Unicode + # letters, combining marks, and numbers. This supports internationalized + # repository paths without admitting controls, separators, bidi formatting, + # shell metacharacters, or Unicode punctuation resembling a path boundary. + allowed_ascii = frozenset("_.@+/ [],()-")''' + if source.count(old) != 1: + raise SystemExit("expected exactly one Strix changed-path policy block") + gate_path.write_text(source.replace(old, new), encoding="utf-8") + PY + + cat > docs/doctoring/strix-legal-git-paths.md <<'MD' + # Strix legal Git path compatibility + + ## Incident and buyer impact + + The organization-required Strix quick gate rejected the exact changed-file list + for `ContextualWisdomLab/aFIPC#160` at head + `804ea97cd83144f94c5020a9d42f2573cc8cb442`. The pull request deletes generated + Packrat artifacts, including the tracked fixture path + `Ugly, but legal, path for a project (long)`. The central gate classified that + path as unsafe solely because its comma and parentheses were absent from the + bounded ASCII allowlist. Security analysis therefore stopped before examining + the pull request, leaving a valid supply-chain cleanup without exact-head Strix + evidence. + + ## Decision + + The normalizer now admits comma and ASCII parentheses. No other punctuation is + broadened. Existing fail-closed controls remain authoritative: + + - empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, + and backslash forms are rejected; + - shell metacharacters such as semicolon, dollar sign, backtick, pipe, and + ampersand remain rejected; + - only the existing Unicode letter, combining-mark, and number categories are + accepted outside ASCII; + - `Path.resolve(strict=False)` followed by `relative_to()` proves containment + beneath the trusted repository root; and + - downstream Git and filesystem operations receive normalized paths as quoted + arguments, never as executable shell source. + + This is a compatibility correction, not a general relaxation to every pathname + byte Git can represent. The privileged scanner intentionally retains a smaller, + audited path policy. + + ## Test-first evidence + + `tests/test_strix_changed_path_policy.py` extracts and executes the exact Python + normalizer embedded in `scripts/ci/strix_quick_gate.sh`. The materializer first + requires the historical Packrat fixture regression to fail on protected main, + then applies the narrow allowlist change and requires the same test to pass. + Permanent tests also preserve established punctuation and reject traversal, + absolute paths, controls, whitespace ambiguity, backslashes, and representative + shell punctuation. + + ## Rollback and incident response + + Roll back the allowlist and regression together only if a downstream call is + proven to evaluate normalized paths as shell source. Until that defect is fixed, + fail Strix closed and retain the offending path, workflow run, and commit SHA as + incident evidence. Do not bypass the required security check. + + ## References + + Git Project. (2026). *Git index format*. https://git-scm.com/docs/index-format + + Git Project. (2026). *git-ls-tree documentation*. https://git-scm.com/docs/git-ls-tree + + Python Software Foundation. (2026). *pathlib—Object-oriented filesystem paths + (Python 3.14.6 documentation)*. https://docs.python.org/3.14/library/pathlib.html + MD + + python3 - <<'PY' + from pathlib import Path + + changelog_path = Path("CHANGELOG.md") + source = changelog_path.read_text(encoding="utf-8") + marker = "### Fixed\n\n" + entry = ( + "- Allowed commas and ASCII parentheses in the bounded Strix changed-file " + "path policy so legal tracked Packrat fixtures can receive exact-head " + "security analysis while traversal, controls, backslashes, whitespace " + "ambiguity, and shell punctuation remain fail-closed.\n" + ) + if source.count(marker) != 1: + raise SystemExit("expected one Unreleased Fixed marker") + changelog_path.write_text(source.replace(marker, marker + entry, 1), encoding="utf-8") + PY + + python3 tests/test_strix_changed_path_policy.py + bash -n scripts/ci/strix_quick_gate.sh + git diff --check + + mapfile -t changed_paths < <( + { + git diff --name-only "$EXPECTED_BASE_SHA" -- + git ls-files --others --exclude-standard + } | sort -u + ) + expected_paths=( + CHANGELOG.md + docs/doctoring/strix-legal-git-paths.md + scripts/ci/strix_quick_gate.sh + tests/test_strix_changed_path_policy.py + "$ORIGINAL_WORKFLOW" + "$REPAIR_WORKFLOW" + ) + mapfile -t expected_paths < <(printf '%s\n' "${expected_paths[@]}" | sort -u) + test "${changed_paths[*]}" = "${expected_paths[*]}" + + rm -- "$ORIGINAL_WORKFLOW" "$REPAIR_WORKFLOW" + git config user.name "CWL Autonomous Development" + git config user.email "actions@users.noreply.github.com" + git add CHANGELOG.md docs/doctoring/strix-legal-git-paths.md scripts/ci/strix_quick_gate.sh tests/test_strix_changed_path_policy.py "$ORIGINAL_WORKFLOW" "$REPAIR_WORKFLOW" + git diff --cached --check + git commit -m "fix(strix): accept legal Packrat fixture paths" + + auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" + git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth_header" \ + push origin "HEAD:refs/heads/$REPAIR_BRANCH" From 4e1e78e2fb905ee7a073762037faaa550d7f1a55 Mon Sep 17 00:00:00 2001 From: CWL Autonomous Development Date: Thu, 6 Aug 2026 07:14:20 +0000 Subject: [PATCH 03/19] fix(strix): accept legal Packrat fixture paths --- .../repair-strix-legal-packrat-paths-v2.yml | 289 ----------------- .../repair-strix-legal-packrat-paths.yml | 290 ------------------ CHANGELOG.md | 1 + docs/doctoring/strix-legal-git-paths.md | 59 ++++ scripts/ci/strix_quick_gate.sh | 15 +- tests/test_strix_changed_path_policy.py | 93 ++++++ 6 files changed, 162 insertions(+), 585 deletions(-) delete mode 100644 .github/workflows/repair-strix-legal-packrat-paths-v2.yml delete mode 100644 .github/workflows/repair-strix-legal-packrat-paths.yml create mode 100644 docs/doctoring/strix-legal-git-paths.md create mode 100644 tests/test_strix_changed_path_policy.py diff --git a/.github/workflows/repair-strix-legal-packrat-paths-v2.yml b/.github/workflows/repair-strix-legal-packrat-paths-v2.yml deleted file mode 100644 index e2872e3e0..000000000 --- a/.github/workflows/repair-strix-legal-packrat-paths-v2.yml +++ /dev/null @@ -1,289 +0,0 @@ -name: Repair legal Strix changed-path materialization - -on: - push: - branches: - - fix/strix-legal-packrat-paths - -permissions: - contents: read - -concurrency: - group: repair-strix-legal-packrat-paths-v2 - cancel-in-progress: false - -jobs: - repair: - if: >- - github.repository == 'ContextualWisdomLab/.github' && - github.ref == 'refs/heads/fix/strix-legal-packrat-paths' - runs-on: ubuntu-24.04 - timeout-minutes: 60 - permissions: - contents: write - env: - EXPECTED_BASE_SHA: f070c504c1cb06891b800d7ab0cf6ac7d3cf8eae - REPAIR_BRANCH: fix/strix-legal-packrat-paths - ORIGINAL_WORKFLOW: .github/workflows/repair-strix-legal-packrat-paths.yml - REPAIR_WORKFLOW: .github/workflows/repair-strix-legal-packrat-paths-v2.yml - steps: - - name: Materialize exact repair branch without persisted credentials - run: | - set -euo pipefail - git init "$GITHUB_WORKSPACE" - git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" - git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=3 origin "$GITHUB_SHA" - git -C "$GITHUB_WORKSPACE" checkout --detach "$GITHUB_SHA" - git -C "$GITHUB_WORKSPACE" merge-base --is-ancestor "$EXPECTED_BASE_SHA" HEAD - mapfile -t committed_paths < <(git -C "$GITHUB_WORKSPACE" diff --name-only "$EXPECTED_BASE_SHA" HEAD | sort -u) - expected_paths=("$ORIGINAL_WORKFLOW" "$REPAIR_WORKFLOW") - mapfile -t expected_paths < <(printf '%s\n' "${expected_paths[@]}" | sort -u) - test "${committed_paths[*]}" = "${expected_paths[*]}" - - - name: Prove RED and publish the verified product commit - env: - GITHUB_TOKEN: ${{ github.token }} - working-directory: ${{ github.workspace }} - run: | - set -euo pipefail - - cat > tests/test_strix_changed_path_policy.py <<'PY' - """Regression tests for the production Strix changed-path normalizer.""" - - from __future__ import annotations - - import subprocess - import sys - import tempfile - import unittest - from pathlib import Path - - - REPOSITORY_ROOT = Path(__file__).resolve().parents[1] - GATE_SCRIPT = REPOSITORY_ROOT / "scripts" / "ci" / "strix_quick_gate.sh" - START_MARKER = 'python3 - "$REPO_ROOT" "$changed_file" <<\'PY\'\n' - END_MARKER = "\nPY\n}\n\nnormalize_changed_files_cache()" - LEGAL_PACKRAT_PATH = ( - "packrat/lib/x86_64-pc-linux-gnu/3.4.1/packrat/tests/testthat/" - "Ugly, but legal, path for a project (long)/bread/DESCRIPTION" - ) - - - def _normalizer_source() -> str: - """Return the exact embedded Python program used in production.""" - - gate_source = GATE_SCRIPT.read_text(encoding="utf-8") - prefix, separator, remainder = gate_source.partition(START_MARKER) - if not separator or not prefix: - raise AssertionError("Strix changed-path normalizer start marker is missing") - source, separator, _suffix = remainder.partition(END_MARKER) - if not separator: - raise AssertionError("Strix changed-path normalizer end marker is missing") - return source - - - def _normalize(candidate: str) -> subprocess.CompletedProcess[str]: - """Execute the production normalizer with an isolated repository root.""" - - with tempfile.TemporaryDirectory() as temporary_directory: - return subprocess.run( - [sys.executable, "-c", _normalizer_source(), temporary_directory, candidate], - check=False, - capture_output=True, - text=True, - ) - - - class StrixChangedPathPolicyTests(unittest.TestCase): - """Verify legal Git paths and fail-closed path boundaries.""" - - def test_accepts_historical_packrat_fixture_path(self) -> None: - """A tracked Packrat fixture with commas and parentheses is valid input.""" - - result = _normalize(LEGAL_PACKRAT_PATH) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout.strip(), LEGAL_PACKRAT_PATH) - - def test_preserves_existing_supported_punctuation(self) -> None: - """Existing bracket, at-sign, plus-sign, space, and hyphen support remains.""" - - candidate = "ui/[slug]/128x128@2x +page-safe/file-name.ts" - result = _normalize(candidate) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout.strip(), candidate) - - def test_rejects_traversal_absolute_controls_and_shell_punctuation(self) -> None: - """The repair does not admit traversal, controls, or shell syntax.""" - - rejected = ( - "", - ".", - "..", - "../secret.txt", - "/tmp/secret.txt", - "safe\\escape.txt", - "safe\nname.txt", - "safe\rname.txt", - " leading.txt", - "trailing.txt ", - "safe;command.txt", - "safe$(command).txt", - "safe`command`.txt", - "safe|command.txt", - "safe&command.txt", - ) - for candidate in rejected: - with self.subTest(candidate=repr(candidate)): - result = _normalize(candidate) - self.assertNotEqual(result.returncode, 0) - self.assertEqual(result.stdout, "") - - - if __name__ == "__main__": - unittest.main() - PY - - set +e - python3 tests/test_strix_changed_path_policy.py >"$RUNNER_TEMP/strix-path-red.log" 2>&1 - red_status=$? - set -e - test "$red_status" -ne 0 - grep -Fq "test_accepts_historical_packrat_fixture_path" "$RUNNER_TEMP/strix-path-red.log" - - python3 - <<'PY' - from pathlib import Path - - gate_path = Path("scripts/ci/strix_quick_gate.sh") - source = gate_path.read_text(encoding="utf-8") - old = '''# '@' is required for Apple/Tauri retina asset names (128x128@2x.png) and '+' - # for SvelteKit's mandatory route files (+page.svelte, +layout.ts). Preserve - # the existing ASCII allowlist and additionally accept only Unicode letters, - # combining marks, and numbers. This supports internationalized repository - # paths without admitting controls, separators, bidi formatting, shell - # metacharacters, or Unicode punctuation that could resemble a path boundary. - allowed_ascii = frozenset("_.@+/ []-")''' - new = '''# '@' is required for Apple/Tauri retina asset names (128x128@2x.png) and '+' - # for SvelteKit's mandatory route files (+page.svelte, +layout.ts). Commas and - # parentheses are ordinary Git filename characters used by historical Packrat - # fixtures. They remain data because downstream filesystem and Git calls pass - # the normalized path as a quoted argument rather than executable shell source. - # Preserve the bounded ASCII allowlist and additionally accept only Unicode - # letters, combining marks, and numbers. This supports internationalized - # repository paths without admitting controls, separators, bidi formatting, - # shell metacharacters, or Unicode punctuation resembling a path boundary. - allowed_ascii = frozenset("_.@+/ [],()-")''' - if source.count(old) != 1: - raise SystemExit("expected exactly one Strix changed-path policy block") - gate_path.write_text(source.replace(old, new), encoding="utf-8") - PY - - cat > docs/doctoring/strix-legal-git-paths.md <<'MD' - # Strix legal Git path compatibility - - ## Incident and buyer impact - - The organization-required Strix quick gate rejected the exact changed-file list - for `ContextualWisdomLab/aFIPC#160` at head - `804ea97cd83144f94c5020a9d42f2573cc8cb442`. The pull request deletes generated - Packrat artifacts, including the tracked fixture path - `Ugly, but legal, path for a project (long)`. The central gate classified that - path as unsafe solely because its comma and parentheses were absent from the - bounded ASCII allowlist. Security analysis therefore stopped before examining - the pull request, leaving a valid supply-chain cleanup without exact-head Strix - evidence. - - ## Decision - - The normalizer now admits comma and ASCII parentheses. No other punctuation is - broadened. Existing fail-closed controls remain authoritative: - - - empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, - and backslash forms are rejected; - - shell metacharacters such as semicolon, dollar sign, backtick, pipe, and - ampersand remain rejected; - - only the existing Unicode letter, combining-mark, and number categories are - accepted outside ASCII; - - `Path.resolve(strict=False)` followed by `relative_to()` proves containment - beneath the trusted repository root; and - - downstream Git and filesystem operations receive normalized paths as quoted - arguments, never as executable shell source. - - This is a compatibility correction, not a general relaxation to every pathname - byte Git can represent. The privileged scanner intentionally retains a smaller, - audited path policy. - - ## Test-first evidence - - `tests/test_strix_changed_path_policy.py` extracts and executes the exact Python - normalizer embedded in `scripts/ci/strix_quick_gate.sh`. The materializer first - requires the historical Packrat fixture regression to fail on protected main, - then applies the narrow allowlist change and requires the same test to pass. - Permanent tests also preserve established punctuation and reject traversal, - absolute paths, controls, whitespace ambiguity, backslashes, and representative - shell punctuation. - - ## Rollback and incident response - - Roll back the allowlist and regression together only if a downstream call is - proven to evaluate normalized paths as shell source. Until that defect is fixed, - fail Strix closed and retain the offending path, workflow run, and commit SHA as - incident evidence. Do not bypass the required security check. - - ## References - - Git Project. (2026). *Git index format*. https://git-scm.com/docs/index-format - - Git Project. (2026). *git-ls-tree documentation*. https://git-scm.com/docs/git-ls-tree - - Python Software Foundation. (2026). *pathlib—Object-oriented filesystem paths - (Python 3.14.6 documentation)*. https://docs.python.org/3.14/library/pathlib.html - MD - - python3 - <<'PY' - from pathlib import Path - - changelog_path = Path("CHANGELOG.md") - source = changelog_path.read_text(encoding="utf-8") - marker = "### Fixed\n\n" - entry = ( - "- Allowed commas and ASCII parentheses in the bounded Strix changed-file " - "path policy so legal tracked Packrat fixtures can receive exact-head " - "security analysis while traversal, controls, backslashes, whitespace " - "ambiguity, and shell punctuation remain fail-closed.\n" - ) - if source.count(marker) != 1: - raise SystemExit("expected one Unreleased Fixed marker") - changelog_path.write_text(source.replace(marker, marker + entry, 1), encoding="utf-8") - PY - - python3 tests/test_strix_changed_path_policy.py - bash -n scripts/ci/strix_quick_gate.sh - git diff --check - - mapfile -t changed_paths < <( - { - git diff --name-only "$EXPECTED_BASE_SHA" -- - git ls-files --others --exclude-standard - } | sort -u - ) - expected_paths=( - CHANGELOG.md - docs/doctoring/strix-legal-git-paths.md - scripts/ci/strix_quick_gate.sh - tests/test_strix_changed_path_policy.py - "$ORIGINAL_WORKFLOW" - "$REPAIR_WORKFLOW" - ) - mapfile -t expected_paths < <(printf '%s\n' "${expected_paths[@]}" | sort -u) - test "${changed_paths[*]}" = "${expected_paths[*]}" - - rm -- "$ORIGINAL_WORKFLOW" "$REPAIR_WORKFLOW" - git config user.name "CWL Autonomous Development" - git config user.email "actions@users.noreply.github.com" - git add CHANGELOG.md docs/doctoring/strix-legal-git-paths.md scripts/ci/strix_quick_gate.sh tests/test_strix_changed_path_policy.py "$ORIGINAL_WORKFLOW" "$REPAIR_WORKFLOW" - git diff --cached --check - git commit -m "fix(strix): accept legal Packrat fixture paths" - - auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" - git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth_header" \ - push origin "HEAD:refs/heads/$REPAIR_BRANCH" diff --git a/.github/workflows/repair-strix-legal-packrat-paths.yml b/.github/workflows/repair-strix-legal-packrat-paths.yml deleted file mode 100644 index 5ccfe5410..000000000 --- a/.github/workflows/repair-strix-legal-packrat-paths.yml +++ /dev/null @@ -1,290 +0,0 @@ -name: Materialize legal Strix changed-path repair - -on: - push: - branches: - - fix/strix-legal-packrat-paths - -permissions: - contents: read - -concurrency: - group: materialize-strix-legal-packrat-paths - cancel-in-progress: false - -jobs: - repair: - if: >- - github.repository == 'ContextualWisdomLab/.github' && - github.ref == 'refs/heads/fix/strix-legal-packrat-paths' - runs-on: ubuntu-24.04 - timeout-minutes: 60 - permissions: - contents: write - steps: - - name: Materialize exact branch without persisted credentials - env: - EXPECTED_BASE_SHA: f070c504c1cb06891b800d7ab0cf6ac7d3cf8eae - REPAIR_WORKFLOW: .github/workflows/repair-strix-legal-packrat-paths.yml - run: | - set -euo pipefail - git init "$GITHUB_WORKSPACE" - git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" - git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=2 origin "$GITHUB_SHA" - git -C "$GITHUB_WORKSPACE" checkout --detach "$GITHUB_SHA" - test "$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD^)" = "$EXPECTED_BASE_SHA" - test "$(git -C "$GITHUB_WORKSPACE" diff --name-only HEAD^ HEAD)" = "$REPAIR_WORKFLOW" - - - name: Prove RED, implement narrow repair, and publish verified product commit - env: - GITHUB_TOKEN: ${{ github.token }} - EXPECTED_BASE_SHA: f070c504c1cb06891b800d7ab0cf6ac7d3cf8eae - REPAIR_BRANCH: fix/strix-legal-packrat-paths - REPAIR_WORKFLOW: .github/workflows/repair-strix-legal-packrat-paths.yml - working-directory: ${{ github.workspace }} - run: | - set -euo pipefail - - cat > tests/test_strix_changed_path_policy.py <<'PY' - """Regression tests for the production Strix changed-path normalizer.""" - - from __future__ import annotations - - import subprocess - import sys - import tempfile - import unittest - from pathlib import Path - - - REPOSITORY_ROOT = Path(__file__).resolve().parents[1] - GATE_SCRIPT = REPOSITORY_ROOT / "scripts" / "ci" / "strix_quick_gate.sh" - START_MARKER = 'python3 - "$REPO_ROOT" "$changed_file" <<\'PY\'\n' - END_MARKER = "\nPY\n}\n\nnormalize_changed_files_cache()" - LEGAL_PACKRAT_PATH = ( - "packrat/lib/x86_64-pc-linux-gnu/3.4.1/packrat/tests/testthat/" - "Ugly, but legal, path for a project (long)/bread/DESCRIPTION" - ) - - - def _normalizer_source() -> str: - """Return the exact embedded Python program used in production.""" - - gate_source = GATE_SCRIPT.read_text(encoding="utf-8") - prefix, separator, remainder = gate_source.partition(START_MARKER) - if not separator or not prefix: - raise AssertionError("Strix changed-path normalizer start marker is missing") - source, separator, _suffix = remainder.partition(END_MARKER) - if not separator: - raise AssertionError("Strix changed-path normalizer end marker is missing") - return source - - - def _normalize(candidate: str) -> subprocess.CompletedProcess[str]: - """Execute the production normalizer with an isolated repository root.""" - - with tempfile.TemporaryDirectory() as temporary_directory: - return subprocess.run( - [ - sys.executable, - "-c", - _normalizer_source(), - temporary_directory, - candidate, - ], - check=False, - capture_output=True, - text=True, - ) - - - class StrixChangedPathPolicyTests(unittest.TestCase): - """Verify legal Git paths and fail-closed path boundaries.""" - - def test_accepts_historical_packrat_fixture_path(self) -> None: - """A tracked Packrat fixture with commas and parentheses is valid input.""" - - result = _normalize(LEGAL_PACKRAT_PATH) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout.strip(), LEGAL_PACKRAT_PATH) - - def test_preserves_existing_supported_punctuation(self) -> None: - """Existing bracket, at-sign, plus-sign, space, and hyphen support remains.""" - - candidate = "ui/[slug]/128x128@2x +page-safe/file-name.ts" - result = _normalize(candidate) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout.strip(), candidate) - - def test_rejects_traversal_absolute_controls_and_shell_punctuation(self) -> None: - """The repair does not admit traversal, controls, or shell syntax.""" - - rejected = ( - "", - ".", - "..", - "../secret.txt", - "/tmp/secret.txt", - "safe\\escape.txt", - "safe\nname.txt", - "safe\rname.txt", - " leading.txt", - "trailing.txt ", - "safe;command.txt", - "safe$(command).txt", - "safe`command`.txt", - "safe|command.txt", - "safe&command.txt", - ) - for candidate in rejected: - with self.subTest(candidate=repr(candidate)): - result = _normalize(candidate) - self.assertNotEqual(result.returncode, 0) - self.assertEqual(result.stdout, "") - - - if __name__ == "__main__": - unittest.main() - PY - - set +e - python3 tests/test_strix_changed_path_policy.py >"$RUNNER_TEMP/strix-path-red.log" 2>&1 - red_status=$? - set -e - if [ "$red_status" -eq 0 ]; then - echo "ERROR: legal Packrat path regression passed before the production repair." >&2 - exit 1 - fi - grep -Fq "test_accepts_historical_packrat_fixture_path" "$RUNNER_TEMP/strix-path-red.log" - - python3 - <<'PY' - from pathlib import Path - - gate_path = Path("scripts/ci/strix_quick_gate.sh") - source = gate_path.read_text(encoding="utf-8") - old = '''# '@' is required for Apple/Tauri retina asset names (128x128@2x.png) and '+' - # for SvelteKit's mandatory route files (+page.svelte, +layout.ts). Preserve - # the existing ASCII allowlist and additionally accept only Unicode letters, - # combining marks, and numbers. This supports internationalized repository - # paths without admitting controls, separators, bidi formatting, shell - # metacharacters, or Unicode punctuation that could resemble a path boundary. - allowed_ascii = frozenset("_.@+/ []-")''' - new = '''# '@' is required for Apple/Tauri retina asset names (128x128@2x.png) and '+' - # for SvelteKit's mandatory route files (+page.svelte, +layout.ts). Commas and - # parentheses are ordinary Git filename characters used by historical Packrat - # fixtures. They remain data because every downstream filesystem and Git call - # passes the normalized path as a quoted argument rather than shell source. - # Preserve the bounded ASCII allowlist and additionally accept only Unicode - # letters, combining marks, and numbers. This supports internationalized - # repository paths without admitting controls, separators, bidi formatting, - # shell metacharacters, or Unicode punctuation resembling a path boundary. - allowed_ascii = frozenset("_.@+/ [],()-")''' - if source.count(old) != 1: - raise SystemExit("expected exactly one Strix changed-path policy block") - gate_path.write_text(source.replace(old, new), encoding="utf-8") - PY - - cat > docs/doctoring/strix-legal-git-paths.md <<'MD' - # Strix legal Git path compatibility - - ## Incident and buyer impact - - The organization-required Strix quick gate rejected the exact changed-file list - for `ContextualWisdomLab/aFIPC#160` at head - `804ea97cd83144f94c5020a9d42f2573cc8cb442`. The pull request deletes generated - Packrat artifacts, including the tracked fixture path - `Ugly, but legal, path for a project (long)`. The central gate classified that - path as unsafe solely because its comma and parentheses were absent from the - bounded ASCII allowlist. Security analysis therefore stopped before examining - the pull request, leaving a valid supply-chain cleanup without exact-head Strix - evidence. - - ## Decision - - The normalizer now admits comma and ASCII parentheses. No other punctuation is - broadened. The existing fail-closed controls remain authoritative: - - - empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, - and backslash forms are rejected; - - shell metacharacters such as semicolon, dollar sign, backtick, pipe, and - ampersand remain rejected; - - only the existing Unicode letter, combining-mark, and number categories are - accepted outside ASCII; - - `Path.resolve(strict=False)` followed by `relative_to()` proves lexical and - symlink-aware containment beneath the trusted repository root; and - - downstream Git and filesystem operations receive the normalized path as a - quoted argument, never as executable shell source. - - This is a compatibility correction, not a general relaxation to every Git - pathname byte. Git can represent a broader set of unusual pathnames, while the - privileged scanner intentionally keeps a smaller audited policy. - - ## Test-first evidence - - `tests/test_strix_changed_path_policy.py` extracts and executes the exact Python - normalizer embedded in `scripts/ci/strix_quick_gate.sh`. The materialization run - first required the historical Packrat fixture regression to fail against the - protected-main implementation, then applied the narrow allowlist change and - required the same test to pass. Permanent tests also preserve the established - punctuation contract and reject traversal, absolute paths, controls, whitespace - ambiguity, backslashes, and representative shell punctuation. - - ## Rollback and incident response - - Roll back the allowlist and its regression together only if a downstream call is - proven to evaluate normalized paths as shell source. Until that defect is fixed, - fail the Strix gate closed and retain the exact offending path, workflow run, - and commit SHA as incident evidence. Do not bypass the required security check. - - ## References - - Git Project. (2026). *Git index format*. https://git-scm.com/docs/index-format - - Git Project. (2026). *git-ls-tree documentation*. https://git-scm.com/docs/git-ls-tree - - Python Software Foundation. (2026). *pathlib—Object-oriented filesystem paths - (Python 3.14.6 documentation)*. https://docs.python.org/3.14/library/pathlib.html - MD - - python3 - <<'PY' - from pathlib import Path - - changelog_path = Path("CHANGELOG.md") - source = changelog_path.read_text(encoding="utf-8") - marker = "### Fixed\n\n" - entry = ( - "- Allowed commas and ASCII parentheses in the bounded Strix changed-file " - "path policy so legal tracked Packrat fixtures can receive exact-head " - "security analysis while traversal, controls, backslashes, whitespace " - "ambiguity, and shell punctuation remain fail-closed.\n" - ) - if source.count(marker) != 1: - raise SystemExit("expected one Unreleased Fixed marker") - changelog_path.write_text(source.replace(marker, marker + entry, 1), encoding="utf-8") - PY - - python3 tests/test_strix_changed_path_policy.py - bash -n scripts/ci/strix_quick_gate.sh - git diff --check - - mapfile -t changed_paths < <(git diff --name-only "$EXPECTED_BASE_SHA" -- | sort) - expected_paths=( - CHANGELOG.md - docs/doctoring/strix-legal-git-paths.md - scripts/ci/strix_quick_gate.sh - tests/test_strix_changed_path_policy.py - "$REPAIR_WORKFLOW" - ) - mapfile -t expected_paths < <(printf '%s\n' "${expected_paths[@]}" | sort) - test "${changed_paths[*]}" = "${expected_paths[*]}" - - rm -- "$REPAIR_WORKFLOW" - git status --short - git config user.name "CWL Autonomous Development" - git config user.email "actions@users.noreply.github.com" - git add CHANGELOG.md docs/doctoring/strix-legal-git-paths.md scripts/ci/strix_quick_gate.sh tests/test_strix_changed_path_policy.py "$REPAIR_WORKFLOW" - git commit -m "fix(strix): accept legal Packrat fixture paths" - - auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" - git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth_header" \ - push origin "HEAD:refs/heads/$REPAIR_BRANCH" diff --git a/CHANGELOG.md b/CHANGELOG.md index e601de81b..7b8589486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,5 +12,6 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis while traversal, controls, backslashes, whitespace ambiguity, and shell punctuation remain fail-closed. - Bound both trusted-uv quality jobs to `github.event.pull_request.head.sha` and added a permanent two-checkout regression contract so exact-head compatibility, coverage, docstring, and compilation claims cannot silently measure GitHub's generated pull-request merge revision. - Made Strix treat only a single LiteLLM provider-error line containing NVIDIA NIM context and model-catalog 404 evidence as cross-model fallback evidence, rejecting cross-line signal assembly and provider-like target source literals; moved the public default to Nemotron 3 Super 120B and added a second NVIDIA hosted candidate before GitHub Models without neutralizing reported vulnerabilities. diff --git a/docs/doctoring/strix-legal-git-paths.md b/docs/doctoring/strix-legal-git-paths.md new file mode 100644 index 000000000..248cc6bc0 --- /dev/null +++ b/docs/doctoring/strix-legal-git-paths.md @@ -0,0 +1,59 @@ +# Strix legal Git path compatibility + +## Incident and buyer impact + +The organization-required Strix quick gate rejected the exact changed-file list +for `ContextualWisdomLab/aFIPC#160` at head +`804ea97cd83144f94c5020a9d42f2573cc8cb442`. The pull request deletes generated +Packrat artifacts, including the tracked fixture path +`Ugly, but legal, path for a project (long)`. The central gate classified that +path as unsafe solely because its comma and parentheses were absent from the +bounded ASCII allowlist. Security analysis therefore stopped before examining +the pull request, leaving a valid supply-chain cleanup without exact-head Strix +evidence. + +## Decision + +The normalizer now admits comma and ASCII parentheses. No other punctuation is +broadened. Existing fail-closed controls remain authoritative: + +- empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, + and backslash forms are rejected; +- shell metacharacters such as semicolon, dollar sign, backtick, pipe, and + ampersand remain rejected; +- only the existing Unicode letter, combining-mark, and number categories are + accepted outside ASCII; +- `Path.resolve(strict=False)` followed by `relative_to()` proves containment + beneath the trusted repository root; and +- downstream Git and filesystem operations receive normalized paths as quoted + arguments, never as executable shell source. + +This is a compatibility correction, not a general relaxation to every pathname +byte Git can represent. The privileged scanner intentionally retains a smaller, +audited path policy. + +## Test-first evidence + +`tests/test_strix_changed_path_policy.py` extracts and executes the exact Python +normalizer embedded in `scripts/ci/strix_quick_gate.sh`. The materializer first +requires the historical Packrat fixture regression to fail on protected main, +then applies the narrow allowlist change and requires the same test to pass. +Permanent tests also preserve established punctuation and reject traversal, +absolute paths, controls, whitespace ambiguity, backslashes, and representative +shell punctuation. + +## Rollback and incident response + +Roll back the allowlist and regression together only if a downstream call is +proven to evaluate normalized paths as shell source. Until that defect is fixed, +fail Strix closed and retain the offending path, workflow run, and commit SHA as +incident evidence. Do not bypass the required security check. + +## References + +Git Project. (2026). *Git index format*. https://git-scm.com/docs/index-format + +Git Project. (2026). *git-ls-tree documentation*. https://git-scm.com/docs/git-ls-tree + +Python Software Foundation. (2026). *pathlib—Object-oriented filesystem paths +(Python 3.14.6 documentation)*. https://docs.python.org/3.14/library/pathlib.html diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index c318f788f..7a1f90de4 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -410,12 +410,15 @@ normalized = posixpath.normpath(relative_path_str) if normalized in (".", "") or normalized.startswith("../") or normalized == "..": raise SystemExit(1) # '@' is required for Apple/Tauri retina asset names (128x128@2x.png) and '+' -# for SvelteKit's mandatory route files (+page.svelte, +layout.ts). Preserve -# the existing ASCII allowlist and additionally accept only Unicode letters, -# combining marks, and numbers. This supports internationalized repository -# paths without admitting controls, separators, bidi formatting, shell -# metacharacters, or Unicode punctuation that could resemble a path boundary. -allowed_ascii = frozenset("_.@+/ []-") +# for SvelteKit's mandatory route files (+page.svelte, +layout.ts). Commas and +# parentheses are ordinary Git filename characters used by historical Packrat +# fixtures. They remain data because downstream filesystem and Git calls pass +# the normalized path as a quoted argument rather than executable shell source. +# Preserve the bounded ASCII allowlist and additionally accept only Unicode +# letters, combining marks, and numbers. This supports internationalized +# repository paths without admitting controls, separators, bidi formatting, +# shell metacharacters, or Unicode punctuation resembling a path boundary. +allowed_ascii = frozenset("_.@+/ [],()-") if not all( (character.isascii() and (character.isalnum() or character in allowed_ascii)) or ( diff --git a/tests/test_strix_changed_path_policy.py b/tests/test_strix_changed_path_policy.py new file mode 100644 index 000000000..23809a2eb --- /dev/null +++ b/tests/test_strix_changed_path_policy.py @@ -0,0 +1,93 @@ +"""Regression tests for the production Strix changed-path normalizer.""" + +from __future__ import annotations + +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +GATE_SCRIPT = REPOSITORY_ROOT / "scripts" / "ci" / "strix_quick_gate.sh" +START_MARKER = 'python3 - "$REPO_ROOT" "$changed_file" <<\'PY\'\n' +END_MARKER = "\nPY\n}\n\nnormalize_changed_files_cache()" +LEGAL_PACKRAT_PATH = ( + "packrat/lib/x86_64-pc-linux-gnu/3.4.1/packrat/tests/testthat/" + "Ugly, but legal, path for a project (long)/bread/DESCRIPTION" +) + + +def _normalizer_source() -> str: + """Return the exact embedded Python program used in production.""" + + gate_source = GATE_SCRIPT.read_text(encoding="utf-8") + prefix, separator, remainder = gate_source.partition(START_MARKER) + if not separator or not prefix: + raise AssertionError("Strix changed-path normalizer start marker is missing") + source, separator, _suffix = remainder.partition(END_MARKER) + if not separator: + raise AssertionError("Strix changed-path normalizer end marker is missing") + return source + + +def _normalize(candidate: str) -> subprocess.CompletedProcess[str]: + """Execute the production normalizer with an isolated repository root.""" + + with tempfile.TemporaryDirectory() as temporary_directory: + return subprocess.run( + [sys.executable, "-c", _normalizer_source(), temporary_directory, candidate], + check=False, + capture_output=True, + text=True, + ) + + +class StrixChangedPathPolicyTests(unittest.TestCase): + """Verify legal Git paths and fail-closed path boundaries.""" + + def test_accepts_historical_packrat_fixture_path(self) -> None: + """A tracked Packrat fixture with commas and parentheses is valid input.""" + + result = _normalize(LEGAL_PACKRAT_PATH) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), LEGAL_PACKRAT_PATH) + + def test_preserves_existing_supported_punctuation(self) -> None: + """Existing bracket, at-sign, plus-sign, space, and hyphen support remains.""" + + candidate = "ui/[slug]/128x128@2x +page-safe/file-name.ts" + result = _normalize(candidate) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), candidate) + + def test_rejects_traversal_absolute_controls_and_shell_punctuation(self) -> None: + """The repair does not admit traversal, controls, or shell syntax.""" + + rejected = ( + "", + ".", + "..", + "../secret.txt", + "/tmp/secret.txt", + "safe\\escape.txt", + "safe\nname.txt", + "safe\rname.txt", + " leading.txt", + "trailing.txt ", + "safe;command.txt", + "safe$(command).txt", + "safe`command`.txt", + "safe|command.txt", + "safe&command.txt", + ) + for candidate in rejected: + with self.subTest(candidate=repr(candidate)): + result = _normalize(candidate) + self.assertNotEqual(result.returncode, 0) + self.assertEqual(result.stdout, "") + + +if __name__ == "__main__": + unittest.main() From abd2ffc313f06f52e80a6d48223403dcc6fbc93f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:17:31 +0900 Subject: [PATCH 04/19] ci(strix): verify legal changed-path policy --- .../strix-changed-path-quality-ci.yml | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/strix-changed-path-quality-ci.yml diff --git a/.github/workflows/strix-changed-path-quality-ci.yml b/.github/workflows/strix-changed-path-quality-ci.yml new file mode 100644 index 000000000..c2cbff60b --- /dev/null +++ b/.github/workflows/strix-changed-path-quality-ci.yml @@ -0,0 +1,44 @@ +name: Strix Changed Path Quality CI + +on: + pull_request: + branches: [main] + paths: + - ".github/workflows/strix-changed-path-quality-ci.yml" + - "scripts/ci/strix_quick_gate.sh" + - "tests/test_strix_changed_path_policy.py" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: strix-changed-path-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + exact-head-path-policy: + if: github.event_name != 'pull_request' || github.event.action != 'closed' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Checkout exact source revision + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + cache: false + + - name: Verify exact-head path policy and syntax + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" + python -m unittest -v tests/test_strix_changed_path_policy.py + python -m compileall -q tests/test_strix_changed_path_policy.py + bash -n scripts/ci/strix_quick_gate.sh + git diff --exit-code From 5e46cbb584f6ed7ba2d7715fb7a7071a4ca072b2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:18:58 +0900 Subject: [PATCH 05/19] fix(ci): use setup-python without cache input --- .github/workflows/strix-changed-path-quality-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/strix-changed-path-quality-ci.yml b/.github/workflows/strix-changed-path-quality-ci.yml index c2cbff60b..b69c90d9a 100644 --- a/.github/workflows/strix-changed-path-quality-ci.yml +++ b/.github/workflows/strix-changed-path-quality-ci.yml @@ -32,7 +32,6 @@ jobs: uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14" - cache: false - name: Verify exact-head path policy and syntax shell: bash --noprofile --norc -e -o pipefail {0} From 000767711914c978601ec04c442255d480fe1aa9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:29:32 +0900 Subject: [PATCH 06/19] test(strix): reject embedded traversal components --- tests/test_strix_changed_path_policy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_strix_changed_path_policy.py b/tests/test_strix_changed_path_policy.py index 23809a2eb..4d5ddd3c4 100644 --- a/tests/test_strix_changed_path_policy.py +++ b/tests/test_strix_changed_path_policy.py @@ -70,6 +70,7 @@ def test_rejects_traversal_absolute_controls_and_shell_punctuation(self) -> None ".", "..", "../secret.txt", + "safe/../target.txt", "/tmp/secret.txt", "safe\\escape.txt", "safe\nname.txt", From c2333af5724047db76ff6ca541982dea11cbea4a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:32:46 +0900 Subject: [PATCH 07/19] test(ci): materialize embedded traversal repair --- .../repair-strix-embedded-traversal.yml | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 .github/workflows/repair-strix-embedded-traversal.yml diff --git a/.github/workflows/repair-strix-embedded-traversal.yml b/.github/workflows/repair-strix-embedded-traversal.yml new file mode 100644 index 000000000..afe5127c4 --- /dev/null +++ b/.github/workflows/repair-strix-embedded-traversal.yml @@ -0,0 +1,194 @@ +name: Materialize embedded Strix traversal repair + +on: + push: + branches: + - fix/strix-legal-packrat-paths + +permissions: + contents: read + +concurrency: + group: materialize-strix-embedded-traversal + cancel-in-progress: false + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/.github' && + github.ref == 'refs/heads/fix/strix-legal-packrat-paths' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: write + env: + EXPECTED_PARENT_SHA: 27f632b5c7d350efdf6a19745eb787268d86e940 + REPAIR_BRANCH: fix/strix-legal-packrat-paths + REPAIR_WORKFLOW: .github/workflows/repair-strix-embedded-traversal.yml + steps: + - name: Materialize exact repair branch without persisted credentials + run: | + set -euo pipefail + git init "$GITHUB_WORKSPACE" + git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" + git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=2 origin "$GITHUB_SHA" + git -C "$GITHUB_WORKSPACE" checkout --detach "$GITHUB_SHA" + test "$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD^)" = "$EXPECTED_PARENT_SHA" + test "$(git -C "$GITHUB_WORKSPACE" diff --name-only HEAD^ HEAD)" = "$REPAIR_WORKFLOW" + + - name: Apply GREEN repair and publish verified product commit + env: + GITHUB_TOKEN: ${{ github.token }} + working-directory: ${{ github.workspace }} + run: | + set -euo pipefail + + python3 - <<'PY' + from pathlib import Path + + gate_path = Path("scripts/ci/strix_quick_gate.sh") + source = gate_path.read_text(encoding="utf-8") + old = '''if "\\\\" in relative_path_str: + raise SystemExit(1) + normalized = posixpath.normpath(relative_path_str)''' + new = '''if "\\\\" in relative_path_str: + raise SystemExit(1) + if any(component == ".." for component in relative_path_str.split("/")): + raise SystemExit(1) + normalized = posixpath.normpath(relative_path_str)''' + if source.count(old) != 1: + raise SystemExit("expected exactly one changed-path normalization boundary") + gate_path.write_text(source.replace(old, new), encoding="utf-8") + PY + + cat > .github/workflows/strix-changed-path-quality-ci.yml <<'YAML' + name: Strix Changed Path Quality CI + + on: + pull_request: + branches: [main] + paths: + - ".github/workflows/strix-changed-path-quality-ci.yml" + - "CHANGELOG.md" + - "docs/doctoring/strix-legal-git-paths.md" + - "scripts/ci/strix_quick_gate.sh" + - "tests/test_strix_changed_path_policy.py" + workflow_dispatch: + + permissions: + contents: read + + concurrency: + group: strix-changed-path-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + + jobs: + exact-head-path-policy: + if: github.event_name != 'pull_request' || github.event.action != 'closed' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Checkout exact source revision + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + + - name: Install exact test runner dependencies + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_NO_INPUT: "1" + run: >- + python -m pip install --only-binary=:all: + coverage==7.15.2 + iniconfig==2.1.0 + packaging==26.2 + pluggy==1.6.0 + pygments==2.20.0 + pytest==9.1.1 + + - name: Verify exact-head path policy and syntax + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" + python -m coverage run -m pytest tests -q + python -m compileall -q tests/test_strix_changed_path_policy.py + bash -n scripts/ci/strix_quick_gate.sh + git diff --exit-code + YAML + + python3 - <<'PY' + from pathlib import Path + + documentation_path = Path("docs/doctoring/strix-legal-git-paths.md") + source = documentation_path.read_text(encoding="utf-8") + old = '''- empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, + and backslash forms are rejected;''' + new = '''- empty, dot, absolute, leading/trailing-whitespace, NUL, CR, LF, and + backslash forms are rejected; + - raw `..` components are rejected before `posixpath.normpath()` can collapse + an embedded traversal such as `safe/../target.txt`;''' + if source.count(old) != 1: + raise SystemExit("expected one documented path boundary") + source = source.replace(old, new) + old_evidence = '''Permanent tests also preserve established punctuation and reject traversal, + absolute paths, controls, whitespace ambiguity, backslashes, and representative + shell punctuation.''' + new_evidence = '''A test-only exact-head commit first demonstrated that `safe/../target.txt` + passed after normalization; the production repair now rejects its raw `..` + component before normalization. Permanent tests preserve established punctuation + and reject traversal, absolute paths, controls, whitespace ambiguity, backslashes, + and representative shell punctuation. The dedicated workflow runs the complete + repository test suite through coverage.py and pytest whenever code or either + authoritative contract document changes.''' + if source.count(old_evidence) != 1: + raise SystemExit("expected one test-first evidence paragraph") + source = source.replace(old_evidence, new_evidence) + references = ''' + + Batchelder, N., & contributors. (2026). *coverage.py 7.15.2* [Computer + software]. Python Package Index. https://pypi.org/project/coverage/7.15.2/ + + pytest development team. (2026). *pytest 9.1.1* [Computer software]. Python + Package Index. https://pypi.org/project/pytest/9.1.1/ + ''' + documentation_path.write_text(source.rstrip() + references, encoding="utf-8") + PY + + python3 - <<'PY' + from pathlib import Path + + changelog_path = Path("CHANGELOG.md") + source = changelog_path.read_text(encoding="utf-8") + old = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis while traversal, controls, backslashes, whitespace ambiguity, and shell punctuation remain fail-closed.''' + new = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed.''' + if source.count(old) != 1: + raise SystemExit("expected one Strix path changelog entry") + changelog_path.write_text(source.replace(old, new), encoding="utf-8") + PY + + python3 -m pip install --disable-pip-version-check --no-input --only-binary=:all: \ + coverage==7.15.2 iniconfig==2.1.0 packaging==26.2 pluggy==1.6.0 \ + pygments==2.20.0 pytest==9.1.1 + python3 -m coverage run -m pytest tests -q + python3 -m compileall -q tests/test_strix_changed_path_policy.py + bash -n scripts/ci/strix_quick_gate.sh + git diff --check + + rm -- "$REPAIR_WORKFLOW" + git config user.name "CWL Autonomous Development" + git config user.email "actions@users.noreply.github.com" + git add .github/workflows/strix-changed-path-quality-ci.yml CHANGELOG.md \ + docs/doctoring/strix-legal-git-paths.md scripts/ci/strix_quick_gate.sh \ + tests/test_strix_changed_path_policy.py "$REPAIR_WORKFLOW" + git diff --cached --check + git commit -m "fix(strix): reject raw traversal components" + + auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" + git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth_header" \ + push origin "HEAD:refs/heads/$REPAIR_BRANCH" From aa433fb996ab7f912d677d30a479ec35ee2117c2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:35:17 +0900 Subject: [PATCH 08/19] fix(ci): repair traversal materializer contract --- .../repair-strix-embedded-traversal-v2.yml | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 .github/workflows/repair-strix-embedded-traversal-v2.yml diff --git a/.github/workflows/repair-strix-embedded-traversal-v2.yml b/.github/workflows/repair-strix-embedded-traversal-v2.yml new file mode 100644 index 000000000..93d9c5b4d --- /dev/null +++ b/.github/workflows/repair-strix-embedded-traversal-v2.yml @@ -0,0 +1,195 @@ +name: Repair embedded Strix traversal materialization + +on: + push: + branches: + - fix/strix-legal-packrat-paths + +permissions: + contents: read + +concurrency: + group: repair-strix-embedded-traversal-v2 + cancel-in-progress: false + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/.github' && + github.ref == 'refs/heads/fix/strix-legal-packrat-paths' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: write + env: + EXPECTED_PARENT_SHA: 1449836fefb53846a0d793862e7aed41f551abee + REPAIR_BRANCH: fix/strix-legal-packrat-paths + FIRST_WORKFLOW: .github/workflows/repair-strix-embedded-traversal.yml + REPAIR_WORKFLOW: .github/workflows/repair-strix-embedded-traversal-v2.yml + steps: + - name: Materialize exact repair branch without persisted credentials + run: | + set -euo pipefail + git init "$GITHUB_WORKSPACE" + git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" + git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=2 origin "$GITHUB_SHA" + git -C "$GITHUB_WORKSPACE" checkout --detach "$GITHUB_SHA" + test "$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD^)" = "$EXPECTED_PARENT_SHA" + test "$(git -C "$GITHUB_WORKSPACE" diff --name-only HEAD^ HEAD)" = "$REPAIR_WORKFLOW" + + - name: Apply GREEN repair and publish verified product commit + env: + GITHUB_TOKEN: ${{ github.token }} + working-directory: ${{ github.workspace }} + run: | + set -euo pipefail + + python3 - <<'PY' + from pathlib import Path + + gate_path = Path("scripts/ci/strix_quick_gate.sh") + source = gate_path.read_text(encoding="utf-8") + old = '''if "\\\\" in relative_path_str: + raise SystemExit(1) + normalized = posixpath.normpath(relative_path_str)''' + new = '''if "\\\\" in relative_path_str: + raise SystemExit(1) + if any(component == ".." for component in relative_path_str.split("/")): + raise SystemExit(1) + normalized = posixpath.normpath(relative_path_str)''' + if source.count(old) != 1: + raise SystemExit("expected exactly one changed-path normalization boundary") + gate_path.write_text(source.replace(old, new), encoding="utf-8") + PY + + cat > .github/workflows/strix-changed-path-quality-ci.yml <<'YAML' + name: Strix Changed Path Quality CI + + on: + pull_request: + branches: [main] + paths: + - ".github/workflows/strix-changed-path-quality-ci.yml" + - "CHANGELOG.md" + - "docs/doctoring/strix-legal-git-paths.md" + - "scripts/ci/strix_quick_gate.sh" + - "tests/test_strix_changed_path_policy.py" + + permissions: + contents: read + + concurrency: + group: strix-changed-path-quality-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + + jobs: + exact-head-path-policy: + if: github.event_name != 'pull_request' || github.event.action != 'closed' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Checkout exact source revision + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + + - name: Install exact test runner dependencies + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_NO_INPUT: "1" + run: >- + python -m pip install --only-binary=:all: + coverage==7.15.2 + iniconfig==2.1.0 + packaging==26.2 + pluggy==1.6.0 + pygments==2.20.0 + pytest==9.1.1 + + - name: Verify exact-head path policy and syntax + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" + python -m coverage run -m pytest tests -q + python -m compileall -q tests/test_strix_changed_path_policy.py + bash -n scripts/ci/strix_quick_gate.sh + git diff --exit-code + YAML + + python3 - <<'PY' + from pathlib import Path + + documentation_path = Path("docs/doctoring/strix-legal-git-paths.md") + source = documentation_path.read_text(encoding="utf-8") + old = '''- empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, + and backslash forms are rejected;''' + new = '''- empty, dot, absolute, leading/trailing-whitespace, NUL, CR, LF, and + backslash forms are rejected; + - raw `..` components are rejected before `posixpath.normpath()` can collapse + an embedded traversal such as `safe/../target.txt`;''' + if source.count(old) != 1: + raise SystemExit("expected one documented path boundary") + source = source.replace(old, new) + old_evidence = '''Permanent tests also preserve established punctuation and reject traversal, + absolute paths, controls, whitespace ambiguity, backslashes, and representative + shell punctuation.''' + new_evidence = '''A test-only exact-head commit first demonstrated that `safe/../target.txt` + passed after normalization; the production repair now rejects its raw `..` + component before normalization. Permanent tests preserve established punctuation + and reject traversal, absolute paths, controls, whitespace ambiguity, backslashes, + and representative shell punctuation. The dedicated workflow runs the complete + repository test suite through coverage.py and pytest whenever code or either + authoritative contract document changes.''' + if source.count(old_evidence) != 1: + raise SystemExit("expected one test-first evidence paragraph") + source = source.replace(old_evidence, new_evidence) + references = ''' + + Batchelder, N., & contributors. (2026). *coverage.py 7.15.2* [Computer + software]. Python Package Index. https://pypi.org/project/coverage/7.15.2/ + + pytest development team. (2026). *pytest 9.1.1* [Computer software]. Python + Package Index. https://pypi.org/project/pytest/9.1.1/ + ''' + documentation_path.write_text(source.rstrip() + references, encoding="utf-8") + PY + + python3 - <<'PY' + from pathlib import Path + + changelog_path = Path("CHANGELOG.md") + source = changelog_path.read_text(encoding="utf-8") + old = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis while traversal, controls, backslashes, whitespace ambiguity, and shell punctuation remain fail-closed.''' + new = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed.''' + if source.count(old) != 1: + raise SystemExit("expected one Strix path changelog entry") + changelog_path.write_text(source.replace(old, new), encoding="utf-8") + PY + + rm -- "$FIRST_WORKFLOW" "$REPAIR_WORKFLOW" + + python3 -m pip install --disable-pip-version-check --no-input --only-binary=:all: \ + coverage==7.15.2 iniconfig==2.1.0 packaging==26.2 pluggy==1.6.0 \ + pygments==2.20.0 pytest==9.1.1 + python3 -m coverage run -m pytest tests -q + python3 -m compileall -q tests/test_strix_changed_path_policy.py + bash -n scripts/ci/strix_quick_gate.sh + git diff --check + + git config user.name "CWL Autonomous Development" + git config user.email "actions@users.noreply.github.com" + git add .github/workflows/strix-changed-path-quality-ci.yml CHANGELOG.md \ + docs/doctoring/strix-legal-git-paths.md scripts/ci/strix_quick_gate.sh \ + tests/test_strix_changed_path_policy.py "$FIRST_WORKFLOW" "$REPAIR_WORKFLOW" + git diff --cached --check + git commit -m "fix(strix): reject raw traversal components" + + auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" + git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth_header" \ + push origin "HEAD:refs/heads/$REPAIR_BRANCH" From f1eabf9184c417e943fa59360fd9f5f955a7238d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:40:01 +0900 Subject: [PATCH 09/19] chore(ci): remove failed traversal materializer --- .../repair-strix-embedded-traversal.yml | 194 ------------------ 1 file changed, 194 deletions(-) delete mode 100644 .github/workflows/repair-strix-embedded-traversal.yml diff --git a/.github/workflows/repair-strix-embedded-traversal.yml b/.github/workflows/repair-strix-embedded-traversal.yml deleted file mode 100644 index afe5127c4..000000000 --- a/.github/workflows/repair-strix-embedded-traversal.yml +++ /dev/null @@ -1,194 +0,0 @@ -name: Materialize embedded Strix traversal repair - -on: - push: - branches: - - fix/strix-legal-packrat-paths - -permissions: - contents: read - -concurrency: - group: materialize-strix-embedded-traversal - cancel-in-progress: false - -jobs: - repair: - if: >- - github.repository == 'ContextualWisdomLab/.github' && - github.ref == 'refs/heads/fix/strix-legal-packrat-paths' - runs-on: ubuntu-24.04 - timeout-minutes: 30 - permissions: - contents: write - env: - EXPECTED_PARENT_SHA: 27f632b5c7d350efdf6a19745eb787268d86e940 - REPAIR_BRANCH: fix/strix-legal-packrat-paths - REPAIR_WORKFLOW: .github/workflows/repair-strix-embedded-traversal.yml - steps: - - name: Materialize exact repair branch without persisted credentials - run: | - set -euo pipefail - git init "$GITHUB_WORKSPACE" - git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" - git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=2 origin "$GITHUB_SHA" - git -C "$GITHUB_WORKSPACE" checkout --detach "$GITHUB_SHA" - test "$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD^)" = "$EXPECTED_PARENT_SHA" - test "$(git -C "$GITHUB_WORKSPACE" diff --name-only HEAD^ HEAD)" = "$REPAIR_WORKFLOW" - - - name: Apply GREEN repair and publish verified product commit - env: - GITHUB_TOKEN: ${{ github.token }} - working-directory: ${{ github.workspace }} - run: | - set -euo pipefail - - python3 - <<'PY' - from pathlib import Path - - gate_path = Path("scripts/ci/strix_quick_gate.sh") - source = gate_path.read_text(encoding="utf-8") - old = '''if "\\\\" in relative_path_str: - raise SystemExit(1) - normalized = posixpath.normpath(relative_path_str)''' - new = '''if "\\\\" in relative_path_str: - raise SystemExit(1) - if any(component == ".." for component in relative_path_str.split("/")): - raise SystemExit(1) - normalized = posixpath.normpath(relative_path_str)''' - if source.count(old) != 1: - raise SystemExit("expected exactly one changed-path normalization boundary") - gate_path.write_text(source.replace(old, new), encoding="utf-8") - PY - - cat > .github/workflows/strix-changed-path-quality-ci.yml <<'YAML' - name: Strix Changed Path Quality CI - - on: - pull_request: - branches: [main] - paths: - - ".github/workflows/strix-changed-path-quality-ci.yml" - - "CHANGELOG.md" - - "docs/doctoring/strix-legal-git-paths.md" - - "scripts/ci/strix_quick_gate.sh" - - "tests/test_strix_changed_path_policy.py" - workflow_dispatch: - - permissions: - contents: read - - concurrency: - group: strix-changed-path-quality-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - - jobs: - exact-head-path-policy: - if: github.event_name != 'pull_request' || github.event.action != 'closed' - runs-on: ubuntu-24.04 - timeout-minutes: 10 - steps: - - name: Checkout exact source revision - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - - - name: Install exact test runner dependencies - env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_INPUT: "1" - run: >- - python -m pip install --only-binary=:all: - coverage==7.15.2 - iniconfig==2.1.0 - packaging==26.2 - pluggy==1.6.0 - pygments==2.20.0 - pytest==9.1.1 - - - name: Verify exact-head path policy and syntax - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" - python -m coverage run -m pytest tests -q - python -m compileall -q tests/test_strix_changed_path_policy.py - bash -n scripts/ci/strix_quick_gate.sh - git diff --exit-code - YAML - - python3 - <<'PY' - from pathlib import Path - - documentation_path = Path("docs/doctoring/strix-legal-git-paths.md") - source = documentation_path.read_text(encoding="utf-8") - old = '''- empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, - and backslash forms are rejected;''' - new = '''- empty, dot, absolute, leading/trailing-whitespace, NUL, CR, LF, and - backslash forms are rejected; - - raw `..` components are rejected before `posixpath.normpath()` can collapse - an embedded traversal such as `safe/../target.txt`;''' - if source.count(old) != 1: - raise SystemExit("expected one documented path boundary") - source = source.replace(old, new) - old_evidence = '''Permanent tests also preserve established punctuation and reject traversal, - absolute paths, controls, whitespace ambiguity, backslashes, and representative - shell punctuation.''' - new_evidence = '''A test-only exact-head commit first demonstrated that `safe/../target.txt` - passed after normalization; the production repair now rejects its raw `..` - component before normalization. Permanent tests preserve established punctuation - and reject traversal, absolute paths, controls, whitespace ambiguity, backslashes, - and representative shell punctuation. The dedicated workflow runs the complete - repository test suite through coverage.py and pytest whenever code or either - authoritative contract document changes.''' - if source.count(old_evidence) != 1: - raise SystemExit("expected one test-first evidence paragraph") - source = source.replace(old_evidence, new_evidence) - references = ''' - - Batchelder, N., & contributors. (2026). *coverage.py 7.15.2* [Computer - software]. Python Package Index. https://pypi.org/project/coverage/7.15.2/ - - pytest development team. (2026). *pytest 9.1.1* [Computer software]. Python - Package Index. https://pypi.org/project/pytest/9.1.1/ - ''' - documentation_path.write_text(source.rstrip() + references, encoding="utf-8") - PY - - python3 - <<'PY' - from pathlib import Path - - changelog_path = Path("CHANGELOG.md") - source = changelog_path.read_text(encoding="utf-8") - old = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis while traversal, controls, backslashes, whitespace ambiguity, and shell punctuation remain fail-closed.''' - new = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed.''' - if source.count(old) != 1: - raise SystemExit("expected one Strix path changelog entry") - changelog_path.write_text(source.replace(old, new), encoding="utf-8") - PY - - python3 -m pip install --disable-pip-version-check --no-input --only-binary=:all: \ - coverage==7.15.2 iniconfig==2.1.0 packaging==26.2 pluggy==1.6.0 \ - pygments==2.20.0 pytest==9.1.1 - python3 -m coverage run -m pytest tests -q - python3 -m compileall -q tests/test_strix_changed_path_policy.py - bash -n scripts/ci/strix_quick_gate.sh - git diff --check - - rm -- "$REPAIR_WORKFLOW" - git config user.name "CWL Autonomous Development" - git config user.email "actions@users.noreply.github.com" - git add .github/workflows/strix-changed-path-quality-ci.yml CHANGELOG.md \ - docs/doctoring/strix-legal-git-paths.md scripts/ci/strix_quick_gate.sh \ - tests/test_strix_changed_path_policy.py "$REPAIR_WORKFLOW" - git diff --cached --check - git commit -m "fix(strix): reject raw traversal components" - - auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" - git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth_header" \ - push origin "HEAD:refs/heads/$REPAIR_BRANCH" From 1fd32ee46807c8d98773880b903ceaab0bbc1964 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:40:13 +0900 Subject: [PATCH 10/19] chore(ci): remove workflow-token repair attempt --- .../repair-strix-embedded-traversal-v2.yml | 195 ------------------ 1 file changed, 195 deletions(-) delete mode 100644 .github/workflows/repair-strix-embedded-traversal-v2.yml diff --git a/.github/workflows/repair-strix-embedded-traversal-v2.yml b/.github/workflows/repair-strix-embedded-traversal-v2.yml deleted file mode 100644 index 93d9c5b4d..000000000 --- a/.github/workflows/repair-strix-embedded-traversal-v2.yml +++ /dev/null @@ -1,195 +0,0 @@ -name: Repair embedded Strix traversal materialization - -on: - push: - branches: - - fix/strix-legal-packrat-paths - -permissions: - contents: read - -concurrency: - group: repair-strix-embedded-traversal-v2 - cancel-in-progress: false - -jobs: - repair: - if: >- - github.repository == 'ContextualWisdomLab/.github' && - github.ref == 'refs/heads/fix/strix-legal-packrat-paths' - runs-on: ubuntu-24.04 - timeout-minutes: 30 - permissions: - contents: write - env: - EXPECTED_PARENT_SHA: 1449836fefb53846a0d793862e7aed41f551abee - REPAIR_BRANCH: fix/strix-legal-packrat-paths - FIRST_WORKFLOW: .github/workflows/repair-strix-embedded-traversal.yml - REPAIR_WORKFLOW: .github/workflows/repair-strix-embedded-traversal-v2.yml - steps: - - name: Materialize exact repair branch without persisted credentials - run: | - set -euo pipefail - git init "$GITHUB_WORKSPACE" - git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" - git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=2 origin "$GITHUB_SHA" - git -C "$GITHUB_WORKSPACE" checkout --detach "$GITHUB_SHA" - test "$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD^)" = "$EXPECTED_PARENT_SHA" - test "$(git -C "$GITHUB_WORKSPACE" diff --name-only HEAD^ HEAD)" = "$REPAIR_WORKFLOW" - - - name: Apply GREEN repair and publish verified product commit - env: - GITHUB_TOKEN: ${{ github.token }} - working-directory: ${{ github.workspace }} - run: | - set -euo pipefail - - python3 - <<'PY' - from pathlib import Path - - gate_path = Path("scripts/ci/strix_quick_gate.sh") - source = gate_path.read_text(encoding="utf-8") - old = '''if "\\\\" in relative_path_str: - raise SystemExit(1) - normalized = posixpath.normpath(relative_path_str)''' - new = '''if "\\\\" in relative_path_str: - raise SystemExit(1) - if any(component == ".." for component in relative_path_str.split("/")): - raise SystemExit(1) - normalized = posixpath.normpath(relative_path_str)''' - if source.count(old) != 1: - raise SystemExit("expected exactly one changed-path normalization boundary") - gate_path.write_text(source.replace(old, new), encoding="utf-8") - PY - - cat > .github/workflows/strix-changed-path-quality-ci.yml <<'YAML' - name: Strix Changed Path Quality CI - - on: - pull_request: - branches: [main] - paths: - - ".github/workflows/strix-changed-path-quality-ci.yml" - - "CHANGELOG.md" - - "docs/doctoring/strix-legal-git-paths.md" - - "scripts/ci/strix_quick_gate.sh" - - "tests/test_strix_changed_path_policy.py" - - permissions: - contents: read - - concurrency: - group: strix-changed-path-quality-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - - jobs: - exact-head-path-policy: - if: github.event_name != 'pull_request' || github.event.action != 'closed' - runs-on: ubuntu-24.04 - timeout-minutes: 10 - steps: - - name: Checkout exact source revision - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - - - name: Install exact test runner dependencies - env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_NO_INPUT: "1" - run: >- - python -m pip install --only-binary=:all: - coverage==7.15.2 - iniconfig==2.1.0 - packaging==26.2 - pluggy==1.6.0 - pygments==2.20.0 - pytest==9.1.1 - - - name: Verify exact-head path policy and syntax - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" - python -m coverage run -m pytest tests -q - python -m compileall -q tests/test_strix_changed_path_policy.py - bash -n scripts/ci/strix_quick_gate.sh - git diff --exit-code - YAML - - python3 - <<'PY' - from pathlib import Path - - documentation_path = Path("docs/doctoring/strix-legal-git-paths.md") - source = documentation_path.read_text(encoding="utf-8") - old = '''- empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, - and backslash forms are rejected;''' - new = '''- empty, dot, absolute, leading/trailing-whitespace, NUL, CR, LF, and - backslash forms are rejected; - - raw `..` components are rejected before `posixpath.normpath()` can collapse - an embedded traversal such as `safe/../target.txt`;''' - if source.count(old) != 1: - raise SystemExit("expected one documented path boundary") - source = source.replace(old, new) - old_evidence = '''Permanent tests also preserve established punctuation and reject traversal, - absolute paths, controls, whitespace ambiguity, backslashes, and representative - shell punctuation.''' - new_evidence = '''A test-only exact-head commit first demonstrated that `safe/../target.txt` - passed after normalization; the production repair now rejects its raw `..` - component before normalization. Permanent tests preserve established punctuation - and reject traversal, absolute paths, controls, whitespace ambiguity, backslashes, - and representative shell punctuation. The dedicated workflow runs the complete - repository test suite through coverage.py and pytest whenever code or either - authoritative contract document changes.''' - if source.count(old_evidence) != 1: - raise SystemExit("expected one test-first evidence paragraph") - source = source.replace(old_evidence, new_evidence) - references = ''' - - Batchelder, N., & contributors. (2026). *coverage.py 7.15.2* [Computer - software]. Python Package Index. https://pypi.org/project/coverage/7.15.2/ - - pytest development team. (2026). *pytest 9.1.1* [Computer software]. Python - Package Index. https://pypi.org/project/pytest/9.1.1/ - ''' - documentation_path.write_text(source.rstrip() + references, encoding="utf-8") - PY - - python3 - <<'PY' - from pathlib import Path - - changelog_path = Path("CHANGELOG.md") - source = changelog_path.read_text(encoding="utf-8") - old = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis while traversal, controls, backslashes, whitespace ambiguity, and shell punctuation remain fail-closed.''' - new = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed.''' - if source.count(old) != 1: - raise SystemExit("expected one Strix path changelog entry") - changelog_path.write_text(source.replace(old, new), encoding="utf-8") - PY - - rm -- "$FIRST_WORKFLOW" "$REPAIR_WORKFLOW" - - python3 -m pip install --disable-pip-version-check --no-input --only-binary=:all: \ - coverage==7.15.2 iniconfig==2.1.0 packaging==26.2 pluggy==1.6.0 \ - pygments==2.20.0 pytest==9.1.1 - python3 -m coverage run -m pytest tests -q - python3 -m compileall -q tests/test_strix_changed_path_policy.py - bash -n scripts/ci/strix_quick_gate.sh - git diff --check - - git config user.name "CWL Autonomous Development" - git config user.email "actions@users.noreply.github.com" - git add .github/workflows/strix-changed-path-quality-ci.yml CHANGELOG.md \ - docs/doctoring/strix-legal-git-paths.md scripts/ci/strix_quick_gate.sh \ - tests/test_strix_changed_path_policy.py "$FIRST_WORKFLOW" "$REPAIR_WORKFLOW" - git diff --cached --check - git commit -m "fix(strix): reject raw traversal components" - - auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" - git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth_header" \ - push origin "HEAD:refs/heads/$REPAIR_BRANCH" From 69f367122bf222255d05cb054d2c792f3ecb9fb1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:40:34 +0900 Subject: [PATCH 11/19] ci(strix): run full exact-head path contract --- .../strix-changed-path-quality-ci.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/strix-changed-path-quality-ci.yml b/.github/workflows/strix-changed-path-quality-ci.yml index b69c90d9a..5da1485a7 100644 --- a/.github/workflows/strix-changed-path-quality-ci.yml +++ b/.github/workflows/strix-changed-path-quality-ci.yml @@ -5,9 +5,10 @@ on: branches: [main] paths: - ".github/workflows/strix-changed-path-quality-ci.yml" + - "CHANGELOG.md" + - "docs/doctoring/strix-legal-git-paths.md" - "scripts/ci/strix_quick_gate.sh" - "tests/test_strix_changed_path_policy.py" - workflow_dispatch: permissions: contents: read @@ -33,11 +34,24 @@ jobs: with: python-version: "3.14" + - name: Install exact test runner dependencies + env: + PIP_DISABLE_PIP_VERSION_CHECK: "1" + PIP_NO_INPUT: "1" + run: >- + python -m pip install --only-binary=:all: + coverage==7.15.2 + iniconfig==2.1.0 + packaging==26.2 + pluggy==1.6.0 + pygments==2.20.0 + pytest==9.1.1 + - name: Verify exact-head path policy and syntax shell: bash --noprofile --norc -e -o pipefail {0} run: | test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" - python -m unittest -v tests/test_strix_changed_path_policy.py + python -m coverage run -m pytest tests -q python -m compileall -q tests/test_strix_changed_path_policy.py bash -n scripts/ci/strix_quick_gate.sh git diff --exit-code From aabdd64a4e4b801321f479d0d2bee644a1a39402 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:41:23 +0900 Subject: [PATCH 12/19] ci(strix): materialize non-workflow traversal repair --- .../repair-strix-embedded-traversal-v3.yml | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 .github/workflows/repair-strix-embedded-traversal-v3.yml diff --git a/.github/workflows/repair-strix-embedded-traversal-v3.yml b/.github/workflows/repair-strix-embedded-traversal-v3.yml new file mode 100644 index 000000000..d8c2c15d5 --- /dev/null +++ b/.github/workflows/repair-strix-embedded-traversal-v3.yml @@ -0,0 +1,137 @@ +name: Materialize non-workflow Strix traversal repair + +on: + push: + branches: + - fix/strix-legal-packrat-paths + +permissions: + contents: read + +concurrency: + group: materialize-strix-embedded-traversal-v3 + cancel-in-progress: false + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/.github' && + github.ref == 'refs/heads/fix/strix-legal-packrat-paths' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: write + env: + EXPECTED_PARENT_SHA: 82c3288e0ed0d54562bc75bd4dbf8ea418c5bf0e + REPAIR_BRANCH: fix/strix-legal-packrat-paths + REPAIR_WORKFLOW: .github/workflows/repair-strix-embedded-traversal-v3.yml + steps: + - name: Materialize exact repair branch without persisted credentials + run: | + set -euo pipefail + git init "$GITHUB_WORKSPACE" + git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" + git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=2 origin "$GITHUB_SHA" + git -C "$GITHUB_WORKSPACE" checkout --detach "$GITHUB_SHA" + test "$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD^)" = "$EXPECTED_PARENT_SHA" + test "$(git -C "$GITHUB_WORKSPACE" diff --name-only HEAD^ HEAD)" = "$REPAIR_WORKFLOW" + + - name: Apply and verify non-workflow repair + env: + GITHUB_TOKEN: ${{ github.token }} + working-directory: ${{ github.workspace }} + run: | + set -euo pipefail + + python3 - <<'PY' + from pathlib import Path + + gate_path = Path("scripts/ci/strix_quick_gate.sh") + source = gate_path.read_text(encoding="utf-8") + old = '''if "\\\\" in relative_path_str: + raise SystemExit(1) + normalized = posixpath.normpath(relative_path_str)''' + new = '''if "\\\\" in relative_path_str: + raise SystemExit(1) + if any(component == ".." for component in relative_path_str.split("/")): + raise SystemExit(1) + normalized = posixpath.normpath(relative_path_str)''' + if source.count(old) != 1: + raise SystemExit("expected exactly one changed-path normalization boundary") + gate_path.write_text(source.replace(old, new), encoding="utf-8") + PY + + python3 - <<'PY' + from pathlib import Path + + documentation_path = Path("docs/doctoring/strix-legal-git-paths.md") + source = documentation_path.read_text(encoding="utf-8") + old = '''- empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, + and backslash forms are rejected;''' + new = '''- empty, dot, absolute, leading/trailing-whitespace, NUL, CR, LF, and + backslash forms are rejected; + - raw `..` components are rejected before `posixpath.normpath()` can collapse + an embedded traversal such as `safe/../target.txt`;''' + if source.count(old) != 1: + raise SystemExit("expected one documented path boundary") + source = source.replace(old, new) + old_evidence = '''Permanent tests also preserve established punctuation and reject traversal, + absolute paths, controls, whitespace ambiguity, backslashes, and representative + shell punctuation.''' + new_evidence = '''A test-only exact-head commit first demonstrated that `safe/../target.txt` + passed after normalization; the production repair now rejects its raw `..` + component before normalization. Permanent tests preserve established punctuation + and reject traversal, absolute paths, controls, whitespace ambiguity, backslashes, + and representative shell punctuation. The dedicated workflow runs the complete + repository test suite through coverage.py and pytest whenever code or either + authoritative contract document changes.''' + if source.count(old_evidence) != 1: + raise SystemExit("expected one test-first evidence paragraph") + source = source.replace(old_evidence, new_evidence) + references = ''' + + Batchelder, N., & contributors. (2026). *coverage.py 7.15.2* [Computer + software]. Python Package Index. https://pypi.org/project/coverage/7.15.2/ + + pytest development team. (2026). *pytest 9.1.1* [Computer software]. Python + Package Index. https://pypi.org/project/pytest/9.1.1/ + ''' + documentation_path.write_text(source.rstrip() + references, encoding="utf-8") + PY + + python3 - <<'PY' + from pathlib import Path + + changelog_path = Path("CHANGELOG.md") + source = changelog_path.read_text(encoding="utf-8") + old = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis while traversal, controls, backslashes, whitespace ambiguity, and shell punctuation remain fail-closed.''' + new = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed.''' + if source.count(old) != 1: + raise SystemExit("expected one Strix path changelog entry") + changelog_path.write_text(source.replace(old, new), encoding="utf-8") + PY + + cp -- "$REPAIR_WORKFLOW" "$RUNNER_TEMP/repair-workflow.yml" + rm -- "$REPAIR_WORKFLOW" + + python3 -m pip install --disable-pip-version-check --no-input --only-binary=:all: \ + coverage==7.15.2 iniconfig==2.1.0 packaging==26.2 pluggy==1.6.0 \ + pygments==2.20.0 pytest==9.1.1 + python3 -m coverage run -m pytest tests -q + python3 -m compileall -q tests/test_strix_changed_path_policy.py + bash -n scripts/ci/strix_quick_gate.sh + git diff --check + + cp -- "$RUNNER_TEMP/repair-workflow.yml" "$REPAIR_WORKFLOW" + git diff --exit-code -- "$REPAIR_WORKFLOW" + + git config user.name "CWL Autonomous Development" + git config user.email "actions@users.noreply.github.com" + git add CHANGELOG.md docs/doctoring/strix-legal-git-paths.md \ + scripts/ci/strix_quick_gate.sh + git diff --cached --check + git commit -m "fix(strix): reject raw traversal components" + + auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" + git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth_header" \ + push origin "HEAD:refs/heads/$REPAIR_BRANCH" From 7338c7910420df098d25a6991b48cac480050bc1 Mon Sep 17 00:00:00 2001 From: CWL Autonomous Development Date: Thu, 6 Aug 2026 07:42:35 +0000 Subject: [PATCH 13/19] fix(strix): reject raw traversal components --- CHANGELOG.md | 2 +- docs/doctoring/strix-legal-git-paths.md | 22 +++++++++++++++++----- scripts/ci/strix_quick_gate.sh | 2 ++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b8589486..5512171d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,6 @@ Semantic Versioning where the repository publishes a release. ### Fixed -- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis while traversal, controls, backslashes, whitespace ambiguity, and shell punctuation remain fail-closed. +- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed. - Bound both trusted-uv quality jobs to `github.event.pull_request.head.sha` and added a permanent two-checkout regression contract so exact-head compatibility, coverage, docstring, and compilation claims cannot silently measure GitHub's generated pull-request merge revision. - Made Strix treat only a single LiteLLM provider-error line containing NVIDIA NIM context and model-catalog 404 evidence as cross-model fallback evidence, rejecting cross-line signal assembly and provider-like target source literals; moved the public default to Nemotron 3 Super 120B and added a second NVIDIA hosted candidate before GitHub Models without neutralizing reported vulnerabilities. diff --git a/docs/doctoring/strix-legal-git-paths.md b/docs/doctoring/strix-legal-git-paths.md index 248cc6bc0..c4ba06ab2 100644 --- a/docs/doctoring/strix-legal-git-paths.md +++ b/docs/doctoring/strix-legal-git-paths.md @@ -17,8 +17,10 @@ evidence. The normalizer now admits comma and ASCII parentheses. No other punctuation is broadened. Existing fail-closed controls remain authoritative: -- empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, - and backslash forms are rejected; +- empty, dot, absolute, leading/trailing-whitespace, NUL, CR, LF, and + backslash forms are rejected; +- raw `..` components are rejected before `posixpath.normpath()` can collapse + an embedded traversal such as `safe/../target.txt`; - shell metacharacters such as semicolon, dollar sign, backtick, pipe, and ampersand remain rejected; - only the existing Unicode letter, combining-mark, and number categories are @@ -38,9 +40,13 @@ audited path policy. normalizer embedded in `scripts/ci/strix_quick_gate.sh`. The materializer first requires the historical Packrat fixture regression to fail on protected main, then applies the narrow allowlist change and requires the same test to pass. -Permanent tests also preserve established punctuation and reject traversal, -absolute paths, controls, whitespace ambiguity, backslashes, and representative -shell punctuation. +A test-only exact-head commit first demonstrated that `safe/../target.txt` +passed after normalization; the production repair now rejects its raw `..` +component before normalization. Permanent tests preserve established punctuation +and reject traversal, absolute paths, controls, whitespace ambiguity, backslashes, +and representative shell punctuation. The dedicated workflow runs the complete +repository test suite through coverage.py and pytest whenever code or either +authoritative contract document changes. ## Rollback and incident response @@ -57,3 +63,9 @@ Git Project. (2026). *git-ls-tree documentation*. https://git-scm.com/docs/git-l Python Software Foundation. (2026). *pathlib—Object-oriented filesystem paths (Python 3.14.6 documentation)*. https://docs.python.org/3.14/library/pathlib.html + +Batchelder, N., & contributors. (2026). *coverage.py 7.15.2* [Computer +software]. Python Package Index. https://pypi.org/project/coverage/7.15.2/ + +pytest development team. (2026). *pytest 9.1.1* [Computer software]. Python +Package Index. https://pypi.org/project/pytest/9.1.1/ diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 7a1f90de4..0f37f3460 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -406,6 +406,8 @@ if "\x00" in relative_path_str: raise SystemExit(1) if "\\" in relative_path_str: raise SystemExit(1) +if any(component == ".." for component in relative_path_str.split("/")): + raise SystemExit(1) normalized = posixpath.normpath(relative_path_str) if normalized in (".", "") or normalized.startswith("../") or normalized == "..": raise SystemExit(1) From d9081cb577570ab5c109f935a6b577e38aafa272 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 6 Aug 2026 16:43:35 +0900 Subject: [PATCH 14/19] chore(ci): remove successful traversal materializer --- .../repair-strix-embedded-traversal-v3.yml | 137 ------------------ 1 file changed, 137 deletions(-) delete mode 100644 .github/workflows/repair-strix-embedded-traversal-v3.yml diff --git a/.github/workflows/repair-strix-embedded-traversal-v3.yml b/.github/workflows/repair-strix-embedded-traversal-v3.yml deleted file mode 100644 index d8c2c15d5..000000000 --- a/.github/workflows/repair-strix-embedded-traversal-v3.yml +++ /dev/null @@ -1,137 +0,0 @@ -name: Materialize non-workflow Strix traversal repair - -on: - push: - branches: - - fix/strix-legal-packrat-paths - -permissions: - contents: read - -concurrency: - group: materialize-strix-embedded-traversal-v3 - cancel-in-progress: false - -jobs: - repair: - if: >- - github.repository == 'ContextualWisdomLab/.github' && - github.ref == 'refs/heads/fix/strix-legal-packrat-paths' - runs-on: ubuntu-24.04 - timeout-minutes: 30 - permissions: - contents: write - env: - EXPECTED_PARENT_SHA: 82c3288e0ed0d54562bc75bd4dbf8ea418c5bf0e - REPAIR_BRANCH: fix/strix-legal-packrat-paths - REPAIR_WORKFLOW: .github/workflows/repair-strix-embedded-traversal-v3.yml - steps: - - name: Materialize exact repair branch without persisted credentials - run: | - set -euo pipefail - git init "$GITHUB_WORKSPACE" - git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" - git -C "$GITHUB_WORKSPACE" fetch --no-tags --depth=2 origin "$GITHUB_SHA" - git -C "$GITHUB_WORKSPACE" checkout --detach "$GITHUB_SHA" - test "$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD^)" = "$EXPECTED_PARENT_SHA" - test "$(git -C "$GITHUB_WORKSPACE" diff --name-only HEAD^ HEAD)" = "$REPAIR_WORKFLOW" - - - name: Apply and verify non-workflow repair - env: - GITHUB_TOKEN: ${{ github.token }} - working-directory: ${{ github.workspace }} - run: | - set -euo pipefail - - python3 - <<'PY' - from pathlib import Path - - gate_path = Path("scripts/ci/strix_quick_gate.sh") - source = gate_path.read_text(encoding="utf-8") - old = '''if "\\\\" in relative_path_str: - raise SystemExit(1) - normalized = posixpath.normpath(relative_path_str)''' - new = '''if "\\\\" in relative_path_str: - raise SystemExit(1) - if any(component == ".." for component in relative_path_str.split("/")): - raise SystemExit(1) - normalized = posixpath.normpath(relative_path_str)''' - if source.count(old) != 1: - raise SystemExit("expected exactly one changed-path normalization boundary") - gate_path.write_text(source.replace(old, new), encoding="utf-8") - PY - - python3 - <<'PY' - from pathlib import Path - - documentation_path = Path("docs/doctoring/strix-legal-git-paths.md") - source = documentation_path.read_text(encoding="utf-8") - old = '''- empty, dot, absolute, traversal, leading/trailing-whitespace, NUL, CR, LF, - and backslash forms are rejected;''' - new = '''- empty, dot, absolute, leading/trailing-whitespace, NUL, CR, LF, and - backslash forms are rejected; - - raw `..` components are rejected before `posixpath.normpath()` can collapse - an embedded traversal such as `safe/../target.txt`;''' - if source.count(old) != 1: - raise SystemExit("expected one documented path boundary") - source = source.replace(old, new) - old_evidence = '''Permanent tests also preserve established punctuation and reject traversal, - absolute paths, controls, whitespace ambiguity, backslashes, and representative - shell punctuation.''' - new_evidence = '''A test-only exact-head commit first demonstrated that `safe/../target.txt` - passed after normalization; the production repair now rejects its raw `..` - component before normalization. Permanent tests preserve established punctuation - and reject traversal, absolute paths, controls, whitespace ambiguity, backslashes, - and representative shell punctuation. The dedicated workflow runs the complete - repository test suite through coverage.py and pytest whenever code or either - authoritative contract document changes.''' - if source.count(old_evidence) != 1: - raise SystemExit("expected one test-first evidence paragraph") - source = source.replace(old_evidence, new_evidence) - references = ''' - - Batchelder, N., & contributors. (2026). *coverage.py 7.15.2* [Computer - software]. Python Package Index. https://pypi.org/project/coverage/7.15.2/ - - pytest development team. (2026). *pytest 9.1.1* [Computer software]. Python - Package Index. https://pypi.org/project/pytest/9.1.1/ - ''' - documentation_path.write_text(source.rstrip() + references, encoding="utf-8") - PY - - python3 - <<'PY' - from pathlib import Path - - changelog_path = Path("CHANGELOG.md") - source = changelog_path.read_text(encoding="utf-8") - old = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis while traversal, controls, backslashes, whitespace ambiguity, and shell punctuation remain fail-closed.''' - new = '''- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed.''' - if source.count(old) != 1: - raise SystemExit("expected one Strix path changelog entry") - changelog_path.write_text(source.replace(old, new), encoding="utf-8") - PY - - cp -- "$REPAIR_WORKFLOW" "$RUNNER_TEMP/repair-workflow.yml" - rm -- "$REPAIR_WORKFLOW" - - python3 -m pip install --disable-pip-version-check --no-input --only-binary=:all: \ - coverage==7.15.2 iniconfig==2.1.0 packaging==26.2 pluggy==1.6.0 \ - pygments==2.20.0 pytest==9.1.1 - python3 -m coverage run -m pytest tests -q - python3 -m compileall -q tests/test_strix_changed_path_policy.py - bash -n scripts/ci/strix_quick_gate.sh - git diff --check - - cp -- "$RUNNER_TEMP/repair-workflow.yml" "$REPAIR_WORKFLOW" - git diff --exit-code -- "$REPAIR_WORKFLOW" - - git config user.name "CWL Autonomous Development" - git config user.email "actions@users.noreply.github.com" - git add CHANGELOG.md docs/doctoring/strix-legal-git-paths.md \ - scripts/ci/strix_quick_gate.sh - git diff --cached --check - git commit -m "fix(strix): reject raw traversal components" - - auth_header="$(printf 'x-access-token:%s' "$GITHUB_TOKEN" | base64 -w0)" - git -c http.https://github.com/.extraheader="AUTHORIZATION: basic $auth_header" \ - push origin "HEAD:refs/heads/$REPAIR_BRANCH" From 4070b81412b6c3d07ce6cbdfff8955d37d146818 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 02:31:06 +0900 Subject: [PATCH 15/19] test(strix): require hash-verified workflow dependencies --- .../test_strix_workflow_dependency_hashes.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/test_strix_workflow_dependency_hashes.py diff --git a/tests/test_strix_workflow_dependency_hashes.py b/tests/test_strix_workflow_dependency_hashes.py new file mode 100644 index 000000000..076ac71aa --- /dev/null +++ b/tests/test_strix_workflow_dependency_hashes.py @@ -0,0 +1,32 @@ +"""Supply-chain contracts for the Strix changed-path policy workflow.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +WORKFLOW = ROOT / ".github" / "workflows" / "strix-changed-path-quality-ci.yml" +EXPECTED_WHEEL_HASHES = { + "coverage==7.15.2": "b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f", + "iniconfig==2.1.0": "9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", + "packaging==26.2": "5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", + "pluggy==1.6.0": "e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", + "pygments==2.20.0": "81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", + "pytest==9.1.1": "37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", +} + + +def test_strix_workflow_installs_only_hash_verified_wheels() -> None: + """Every network-installed test dependency is versioned and hash verified.""" + workflow = WORKFLOW.read_text(encoding="utf-8") + + assert "--only-binary=:all:" in workflow + assert "--require-hashes" in workflow + for requirement, digest in EXPECTED_WHEEL_HASHES.items(): + assert f"{requirement} --hash=sha256:{digest}" in workflow + + +def test_strix_workflow_reruns_when_hash_contract_changes() -> None: + """Changing this regression contract must trigger the exact-head workflow.""" + workflow = WORKFLOW.read_text(encoding="utf-8") + + assert ' - "tests/test_strix_workflow_dependency_hashes.py"' in workflow From 23479a965264c36a67c462848769d5d5842da440 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 02:31:30 +0900 Subject: [PATCH 16/19] fix(strix): hash-pin exact workflow wheels --- .../strix-changed-path-quality-ci.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/strix-changed-path-quality-ci.yml b/.github/workflows/strix-changed-path-quality-ci.yml index 5da1485a7..1bc804c00 100644 --- a/.github/workflows/strix-changed-path-quality-ci.yml +++ b/.github/workflows/strix-changed-path-quality-ci.yml @@ -9,6 +9,7 @@ on: - "docs/doctoring/strix-legal-git-paths.md" - "scripts/ci/strix_quick_gate.sh" - "tests/test_strix_changed_path_policy.py" + - "tests/test_strix_workflow_dependency_hashes.py" permissions: contents: read @@ -34,24 +35,24 @@ jobs: with: python-version: "3.14" - - name: Install exact test runner dependencies + - name: Install exact hash-verified test runner dependencies env: PIP_DISABLE_PIP_VERSION_CHECK: "1" PIP_NO_INPUT: "1" run: >- - python -m pip install --only-binary=:all: - coverage==7.15.2 - iniconfig==2.1.0 - packaging==26.2 - pluggy==1.6.0 - pygments==2.20.0 - pytest==9.1.1 + python -m pip install --only-binary=:all: --require-hashes + coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f + iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e + pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 + pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c - name: Verify exact-head path policy and syntax shell: bash --noprofile --norc -e -o pipefail {0} run: | test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" python -m coverage run -m pytest tests -q - python -m compileall -q tests/test_strix_changed_path_policy.py + python -m compileall -q tests/test_strix_changed_path_policy.py tests/test_strix_workflow_dependency_hashes.py bash -n scripts/ci/strix_quick_gate.sh git diff --exit-code From 5661987576ca9718d19644e762592174bfd485b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 02:32:09 +0900 Subject: [PATCH 17/19] docs(doctoring): record Strix wheel hash boundary --- docs/doctoring/strix-legal-git-paths.md | 49 ++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/docs/doctoring/strix-legal-git-paths.md b/docs/doctoring/strix-legal-git-paths.md index c4ba06ab2..aa68f8e7f 100644 --- a/docs/doctoring/strix-legal-git-paths.md +++ b/docs/doctoring/strix-legal-git-paths.md @@ -48,6 +48,30 @@ and representative shell punctuation. The dedicated workflow runs the complete repository test suite through coverage.py and pytest whenever code or either authoritative contract document changes. +## Workflow dependency integrity + +The exact-head policy workflow downloads its Python test runner from PyPI, so +version pins alone are insufficient: a compromised index response or replaced +artifact could otherwise change executable CI code without a repository diff. +The workflow therefore uses pip hash-checking mode (`--require-hashes`) together +with `--only-binary=:all:` and the exact SHA-256 digest of every wheel selected +on the fixed `ubuntu-24.04` x86-64 / CPython 3.14 runner: + +- coverage 7.15.2: `b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f`; +- iniconfig 2.1.0: `9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760`; +- packaging 26.2: `5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e`; +- pluggy 1.6.0: `e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746`; +- Pygments 2.20.0: `81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176`; +- pytest 9.1.1: `37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c`. + +`tests/test_strix_workflow_dependency_hashes.py` was committed before the +workflow implementation and fails against the preceding exact head because +hash-checking mode and its trigger path are absent. It is now part of the +workflow's own path filter and verifies every requirement/digest pair. Any +package or runner-platform change must update the package version, PyPI wheel +digest, regression contract, and this record together. A digest mismatch must +fail closed; do not disable hash checking to restore availability. + ## Rollback and incident response Roll back the allowlist and regression together only if a downstream call is @@ -55,17 +79,38 @@ proven to evaluate normalized paths as shell source. Until that defect is fixed, fail Strix closed and retain the offending path, workflow run, and commit SHA as incident evidence. Do not bypass the required security check. +If an exact dependency wheel becomes unavailable, first verify the release and +artifact digest against PyPI's file record and provenance. A rollback may select +the last known-good fully versioned wheel only when its exact hash is recorded in +the workflow, regression contract, and this document. Never replace +`--require-hashes` with an unhashed install. + ## References +Batchelder, N., & contributors. (2026). *coverage.py 7.15.2* [Computer +software]. Python Package Index. https://pypi.org/project/coverage/7.15.2/ + Git Project. (2026). *Git index format*. https://git-scm.com/docs/index-format Git Project. (2026). *git-ls-tree documentation*. https://git-scm.com/docs/git-ls-tree +Python Packaging Authority. (2026). *Secure installs*. pip documentation. +https://pip.pypa.io/en/stable/topics/secure-installs/ + Python Software Foundation. (2026). *pathlib—Object-oriented filesystem paths (Python 3.14.6 documentation)*. https://docs.python.org/3.14/library/pathlib.html -Batchelder, N., & contributors. (2026). *coverage.py 7.15.2* [Computer -software]. Python Package Index. https://pypi.org/project/coverage/7.15.2/ +pytest development team. (2025). *iniconfig 2.1.0* [Computer software]. Python +Package Index. https://pypi.org/project/iniconfig/2.1.0/ + +pytest development team. (2025). *pluggy 1.6.0* [Computer software]. Python +Package Index. https://pypi.org/project/pluggy/1.6.0/ pytest development team. (2026). *pytest 9.1.1* [Computer software]. Python Package Index. https://pypi.org/project/pytest/9.1.1/ + +Python Packaging Authority. (2026). *packaging 26.2* [Computer software]. +Python Package Index. https://pypi.org/project/packaging/26.2/ + +Pygments contributors. (2026). *Pygments 2.20.0* [Computer software]. Python +Package Index. https://pypi.org/project/Pygments/2.20.0/ From c909cea018f10aa90bcfb7fa6e0dca48baab86e4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 08:27:25 +0900 Subject: [PATCH 18/19] test(strix): require hash file installation contract --- tests/test_strix_workflow_dependency_hashes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_strix_workflow_dependency_hashes.py b/tests/test_strix_workflow_dependency_hashes.py index 076ac71aa..eeda4d1b8 100644 --- a/tests/test_strix_workflow_dependency_hashes.py +++ b/tests/test_strix_workflow_dependency_hashes.py @@ -21,6 +21,8 @@ def test_strix_workflow_installs_only_hash_verified_wheels() -> None: assert "--only-binary=:all:" in workflow assert "--require-hashes" in workflow + assert 'cat >"${RUNNER_TEMP}/strix-quality-requirements.txt"' in workflow + assert '-r "${RUNNER_TEMP}/strix-quality-requirements.txt"' in workflow for requirement, digest in EXPECTED_WHEEL_HASHES.items(): assert f"{requirement} --hash=sha256:{digest}" in workflow From c51bf92cfa614c83d63bee73bc069ebd242a941c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 7 Aug 2026 08:27:45 +0900 Subject: [PATCH 19/19] fix(strix): install hash pins from requirements file --- .github/workflows/strix-changed-path-quality-ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/strix-changed-path-quality-ci.yml b/.github/workflows/strix-changed-path-quality-ci.yml index 1bc804c00..cddf5baa1 100644 --- a/.github/workflows/strix-changed-path-quality-ci.yml +++ b/.github/workflows/strix-changed-path-quality-ci.yml @@ -39,14 +39,20 @@ jobs: env: PIP_DISABLE_PIP_VERSION_CHECK: "1" PIP_NO_INPUT: "1" - run: >- - python -m pip install --only-binary=:all: --require-hashes + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + cat >"${RUNNER_TEMP}/strix-quality-requirements.txt" <<'EOF' coverage==7.15.2 --hash=sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c + EOF + python -m pip install \ + --only-binary=:all: \ + --require-hashes \ + -r "${RUNNER_TEMP}/strix-quality-requirements.txt" - name: Verify exact-head path policy and syntax shell: bash --noprofile --norc -e -o pipefail {0}