From 672cbbe676b68e7a97a29e82eb94d04d339560ad Mon Sep 17 00:00:00 2001 From: rene-founder Date: Tue, 7 Jul 2026 15:25:38 +0100 Subject: [PATCH 1/9] fix(g19): close PR14 P2 review-thread gaps --- .github/workflows/ci.yml | 6 ++--- ...nt-gate-inline-git-checkout-before-run.yml | 22 +++++++++++++++++ ...restep-command-git-checkout-head-drift.yml | 24 +++++++++++++++++++ .../mutant-prestep-truncate-run-gates.yml | 24 +++++++++++++++++++ ...t-sentinel-printf-redirect-side-effect.yml | 22 +++++++++++++++++ ...mutant-workflow-dispatch-flow-sequence.yml | 22 +++++++++++++++++ tests/g19-v2-structural-check.sh | 14 +++++++---- tests/run-gates.sh | 12 +++++++++- 8 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/g19-v2/mutant-gate-inline-git-checkout-before-run.yml create mode 100644 tests/fixtures/g19-v2/mutant-prestep-command-git-checkout-head-drift.yml create mode 100644 tests/fixtures/g19-v2/mutant-prestep-truncate-run-gates.yml create mode 100644 tests/fixtures/g19-v2/mutant-sentinel-printf-redirect-side-effect.yml create mode 100644 tests/fixtures/g19-v2/mutant-workflow-dispatch-flow-sequence.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7131c96..0a7d33d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,12 +100,12 @@ jobs: - name: G-19 CI anti-theater (run-gates execution proof required) if: always() env: - VT_G19_EXPECTED_RUN_GATES_SHA: "7dd87f48c59695a0985fcebcb916122c83150e44148592f81233892dd78804f1" - VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "87d6f13710320d0178767664e6d37fc1a8e1c49e214fffab8d6ac9c44b705109" + VT_G19_EXPECTED_RUN_GATES_SHA: "4a886170579c6b2eb6d910578b04ca42c9fa152209c4f902a950a68c0a274491" + VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "795dbee3bb62ade56dc6ba17868302d50cb887b152e1ec1a752b1f8bc2929b90" VT_G19_EXPECTED_WORKSPACE_HELPER_SHA: "ecc05ac6690df69f5d1db7986bea397368fef247f2145c5965401d7b97384e31" VT_G19_EXPECTED_VERIFY_PY_SHA: "abedd126eac0227dddad9267d8863d1b3b4921ef0bdc69301f59b9c9d708683b" VT_G19_EXPECTED_VERIFY_SH_SHA: "65694b5e0abe95f3322c45ab7c69b597d83121ec14001bdbba8e6e83f2224192" - VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "82ab4918f9032d76ff0464e0fa5d75309374ef36c64765458354586f1598ea0a" + VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "d3efcdd8c4a612097a30aa3c98d3862cdfe78fe9fef5510568bea9fe49bb23b4" VT_G19_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} VT_G19_PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before || github.sha }} VT_G19_CHECKOUT_SHA: ${{ github.sha }} diff --git a/tests/fixtures/g19-v2/mutant-gate-inline-git-checkout-before-run.yml b/tests/fixtures/g19-v2/mutant-gate-inline-git-checkout-before-run.yml new file mode 100644 index 0000000..7f43b87 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-gate-inline-git-checkout-before-run.yml @@ -0,0 +1,22 @@ +name: Verification Tools CI +jobs: + gates: + steps: + - name: Run verification gate suite + id: run_gates + run: | + git checkout HEAD~1 + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi \ No newline at end of file diff --git a/tests/fixtures/g19-v2/mutant-prestep-command-git-checkout-head-drift.yml b/tests/fixtures/g19-v2/mutant-prestep-command-git-checkout-head-drift.yml new file mode 100644 index 0000000..14acb91 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-prestep-command-git-checkout-head-drift.yml @@ -0,0 +1,24 @@ +name: Verification Tools CI +jobs: + gates: + steps: + - name: Move git checkout before gate + run: | + command git checkout HEAD~1 + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi \ No newline at end of file diff --git a/tests/fixtures/g19-v2/mutant-prestep-truncate-run-gates.yml b/tests/fixtures/g19-v2/mutant-prestep-truncate-run-gates.yml new file mode 100644 index 0000000..5b9921c --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-prestep-truncate-run-gates.yml @@ -0,0 +1,24 @@ +name: Verification Tools CI +jobs: + gates: + steps: + - name: Truncate run-gates before gate + run: | + truncate -s 0 tests/run-gates.sh + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi \ No newline at end of file diff --git a/tests/fixtures/g19-v2/mutant-sentinel-printf-redirect-side-effect.yml b/tests/fixtures/g19-v2/mutant-sentinel-printf-redirect-side-effect.yml new file mode 100644 index 0000000..fe61936 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-sentinel-printf-redirect-side-effect.yml @@ -0,0 +1,22 @@ +name: Verification Tools CI +jobs: + gates: + steps: + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + printf pwned > /tmp/pwned + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi \ No newline at end of file diff --git a/tests/fixtures/g19-v2/mutant-workflow-dispatch-flow-sequence.yml b/tests/fixtures/g19-v2/mutant-workflow-dispatch-flow-sequence.yml new file mode 100644 index 0000000..ab49fd2 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-workflow-dispatch-flow-sequence.yml @@ -0,0 +1,22 @@ +name: Verification Tools CI +on: [pull_request, workflow_dispatch] +jobs: + gates: + steps: + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi \ No newline at end of file diff --git a/tests/g19-v2-structural-check.sh b/tests/g19-v2-structural-check.sh index f933787..03f540d 100644 --- a/tests/g19-v2-structural-check.sh +++ b/tests/g19-v2-structural-check.sh @@ -223,6 +223,10 @@ for index, line in enumerate(lines): parsed = parse_key(line) if parsed and parsed["key"] == "workflow_dispatch": fail("workflow_dispatch must not be enabled for the required G-19 proof workflow") + if parsed and parsed["key"] == "on": + on_value = strip_quotes(parsed["value"]) + if re.search(r"(^|[,\[\s])workflow_dispatch([,\]\s]|$)", on_value): + fail("workflow_dispatch must not be enabled for the required G-19 proof workflow") if len(top_jobs) != 1: fail(f"expected exactly one top-level jobs: key, found {len(top_jobs)}") @@ -796,12 +800,12 @@ def forbidden_environment_mutation(step): ) for protected_path in protected_paths: if protected_path in raw_code or protected_path in code: - if re.search(r'(^|[;&|]\s*)(cp|mv|install)\b', code): + if re.search(r'(^|[;&|]\s*)(cp|mv|install|truncate|dd)\b', code): return "tracked gate file" if re.search(r'(^|[;&|]\s*)(cat|printf|echo|tee|python[0-9.]*|node|perl|ruby|sed)\b', code) and re.search(r'(>>?|--in-place|-i\b)', code): return "tracked gate file" if step["name"] not in {GATE_STEP_NAME, SENTINEL_STEP_NAME}: - if re.search(r'(^|[;&|]\s*)git\s+(checkout|switch|reset|clean|restore|update-ref|worktree|clone|fetch|pull|merge|rebase|submodule|apply|am|commit|add|rm|mv)\b', code): + if re.search(r'(^|[;&|]\s*)(?:(?:command|builtin)\s+)?git\s+(checkout|switch|reset|clean|restore|update-ref|worktree|clone|fetch|pull|merge|rebase|submodule|apply|am|commit|add|rm|mv)\b', code): return "git checkout state" if "$(" in raw_code or "`" in raw_code: return "computed environment file" @@ -972,11 +976,11 @@ if top_jobs_index is not None: fail("gate execution step must not define working-directory") if gate["run_style"] == ">": fail("folded scalar run: > found on gate execution step") - if gate_exec[-1:] != ["bash tests/run-gates.sh"]: - fail("gate run block must end with executable line: bash tests/run-gates.sh") if any(contains_neutralizer(line) for line in gate_exec): fail("gate execution step contains gate-neutralizing pattern(s)") strict_gate_sequence = strict_workflow_proof or any("MATERIALIZED_WORKTREE" in line for line in gate_exec) + if not strict_gate_sequence and gate_exec != ["bash tests/run-gates.sh"]: + fail("gate run block must be exactly: bash tests/run-gates.sh") if strict_gate_sequence: expected_gate_exec = [ 'ROOT_WORKSPACE="$PWD"', @@ -1072,7 +1076,7 @@ if top_jobs_index is not None: if any(line in {"true", ":", "exit 0"} for line in sentinel_exec): fail("sentinel contains inert success command") if not strict_workflow_proof and any( - re.search(r'(^|[;&|]\s*)(touch|cp|mv|rm|python[0-9.]*|node|perl|ruby|sed|cat|tee|bash|sh|git)\b', line) + re.search(r'(^|[;&|]\s*)(touch|cp|mv|rm|printf|echo|python[0-9.]*|node|perl|ruby|sed|cat|tee|bash|sh|git)\b', line) for line in sentinel_exec ): fail("sentinel contains side-effecting command outside the proof contract") diff --git a/tests/run-gates.sh b/tests/run-gates.sh index 9e7624d..1c490cd 100644 --- a/tests/run-gates.sh +++ b/tests/run-gates.sh @@ -256,7 +256,7 @@ ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-alia ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|baseline-run-folded2-safe|baseline-run-pipe2-safe|mutant-prestep-run-folded2-line2-github-env|mutant-prestep-run-folded9-chomp-github-output-proof|mutant-prestep-run-folded9-line2-github-output-proof|mutant-prestep-run-pipe2-chomp-github-env|mutant-prestep-run-pipe2-line2-bashenv|mutant-prestep-run-pipe2-line2-github-env|mutant-prestep-run-pipe-chomp2-github-env|mutant-prestep-run-pipe9-line2-github-output-proof}" ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-prestep-list-item-merge-poison-run|mutant-prestep-list-item-merge-poison-env|mutant-prestep-list-item-merge-poison-uses|mutant-prestep-block-ansi-u005f-env|mutant-prestep-block-ansi-x5f-env|mutant-prestep-block-ansi-u005f-output-proof|mutant-prestep-block-ansi-u005f-path|mutant-sentinel-declare-hex-n-nameref|mutant-sentinel-local-hex-n-nameref|mutant-sentinel-typeset-hex-n-nameref|mutant-sentinel-declare-ansi-n-nameref|mutant-prestep-usrbin-bash-c-github-env|mutant-prestep-env-i-bash-c-github-output-proof|mutant-prestep-sh-c-github-env|mutant-prestep-tab-indented-block-github-env}" ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-inline-anonymous-run-pipe2-chomp-github-env|mutant-inline-run-plain-continuation-github-env-bashenv|mutant-inline-run-plain-continuation-github-output-proof|mutant-inline-run-single-quoted-continuation-github-env-bashenv|mutant-inline-run-single-quoted-continuation-github-output-proof}" -ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|baseline-no-working-directory-safe|mutant-prestep-shadow-run-gates-stub|mutant-prestep-shadow-structural-checker-stub|mutant-prestep-shadow-verifier-stub|mutant-step-working-directory-shadow-tree|mutant-job-working-directory-shadow-tree|mutant-prestep-block-ansi-octal-137-env|mutant-prestep-block-ansi-octal-137-output-proof|mutant-prestep-block-ansi-octal-137-path|mutant-prestep-block-ansi-octal-bashenv|mutant-prestep-source-github-env|mutant-prestep-dot-github-env|mutant-inline-run-single-quoted-doubled-quote-github-env|mutant-sentinel-proof-variable-option-plain-nameref-overwrite|mutant-prestep-command-substitution-bash-c-github-env|mutant-prestep-bash-ec-github-env|mutant-prestep-process-substitution-bash-github-env|mutant-prestep-git-checkout-head-drift|mutant-gate-post-verify-run-gates-rewrite|mutant-gate-missing-ci-yml-verify|mutant-gate-missing-helper-source|mutant-sentinel-extra-command|mutant-sentinel-v3-proof-preimage|mutant-workflow-dispatch-enabled|mutant-sentinel-expected-proof-overwrite}" +ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|baseline-no-working-directory-safe|mutant-prestep-shadow-run-gates-stub|mutant-prestep-shadow-structural-checker-stub|mutant-prestep-shadow-verifier-stub|mutant-step-working-directory-shadow-tree|mutant-job-working-directory-shadow-tree|mutant-prestep-block-ansi-octal-137-env|mutant-prestep-block-ansi-octal-137-output-proof|mutant-prestep-block-ansi-octal-137-path|mutant-prestep-block-ansi-octal-bashenv|mutant-prestep-source-github-env|mutant-prestep-dot-github-env|mutant-inline-run-single-quoted-doubled-quote-github-env|mutant-sentinel-proof-variable-option-plain-nameref-overwrite|mutant-prestep-command-substitution-bash-c-github-env|mutant-prestep-bash-ec-github-env|mutant-prestep-process-substitution-bash-github-env|mutant-prestep-git-checkout-head-drift|mutant-gate-post-verify-run-gates-rewrite|mutant-gate-missing-ci-yml-verify|mutant-gate-missing-helper-source|mutant-sentinel-extra-command|mutant-sentinel-v3-proof-preimage|mutant-workflow-dispatch-enabled|mutant-workflow-dispatch-flow-sequence|mutant-prestep-command-git-checkout-head-drift|mutant-sentinel-printf-redirect-side-effect|mutant-prestep-truncate-run-gates|mutant-gate-inline-git-checkout-before-run|mutant-sentinel-expected-proof-overwrite}" TRACKED_FILES="$WORK/tracked-files.txt" if ! git ls-files >"$TRACKED_FILES"; then fail "G-18 tracked file scan completed" "git ls-files failed" @@ -428,6 +428,11 @@ mutant-gate-post-verify-run-gates-rewrite.yml mutant-sentinel-extra-command.yml mutant-sentinel-v3-proof-preimage.yml mutant-workflow-dispatch-enabled.yml +mutant-workflow-dispatch-flow-sequence.yml +mutant-prestep-command-git-checkout-head-drift.yml +mutant-sentinel-printf-redirect-side-effect.yml +mutant-prestep-truncate-run-gates.yml +mutant-gate-inline-git-checkout-before-run.yml mutant-sentinel-expected-proof-overwrite.yml mutant-sentinel-proof-variable-option-plain-nameref-overwrite.yml mutant-step-working-directory-shadow-tree.yml @@ -599,6 +604,11 @@ if [ "$FAILED" -eq 0 ]; then exit 2 fi done + EXECUTED_HEAD_SHA="$(git rev-parse HEAD)" + if [ "$PROOF_CHECKOUT_SHA" != "$EXECUTED_HEAD_SHA" ]; then + echo "SETUP FAIL: VT_G19_CHECKOUT_SHA does not match executed worktree HEAD ($EXECUTED_HEAD_SHA)" + exit 2 + fi tracked_blob_sha256() { git cat-file blob "$PROOF_CHECKOUT_SHA:$1" | sha256sum | awk '{print $1}' } From 3387dbb3e26deb2c211c7e427c6e9ea21c167981 Mon Sep 17 00:00:00 2001 From: rene-founder Date: Tue, 7 Jul 2026 15:48:10 +0100 Subject: [PATCH 2/9] fix(g19): harden P2 gaps found by adversarial red-team --- .github/workflows/ci.yml | 6 ++--- .../mutant-prestep-colon-redir-run-gates.yml | 24 +++++++++++++++++++ ...nt-prestep-env-git-checkout-head-drift.yml | 24 +++++++++++++++++++ ...tant-prestep-git-C-checkout-head-drift.yml | 24 +++++++++++++++++++ .../g19-v2/mutant-prestep-tee-run-gates.yml | 24 +++++++++++++++++++ ...prestep-usrbin-git-checkout-head-drift.yml | 24 +++++++++++++++++++ ...nt-sentinel-builtin-printf-side-effect.yml | 22 +++++++++++++++++ ...utant-sentinel-exec-printf-side-effect.yml | 22 +++++++++++++++++ tests/g19-v2-structural-check.sh | 16 +++++++++---- tests/run-gates.sh | 20 ++++++++++++---- 10 files changed, 195 insertions(+), 11 deletions(-) create mode 100644 tests/fixtures/g19-v2/mutant-prestep-colon-redir-run-gates.yml create mode 100644 tests/fixtures/g19-v2/mutant-prestep-env-git-checkout-head-drift.yml create mode 100644 tests/fixtures/g19-v2/mutant-prestep-git-C-checkout-head-drift.yml create mode 100644 tests/fixtures/g19-v2/mutant-prestep-tee-run-gates.yml create mode 100644 tests/fixtures/g19-v2/mutant-prestep-usrbin-git-checkout-head-drift.yml create mode 100644 tests/fixtures/g19-v2/mutant-sentinel-builtin-printf-side-effect.yml create mode 100644 tests/fixtures/g19-v2/mutant-sentinel-exec-printf-side-effect.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a7d33d..88bad51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,12 +100,12 @@ jobs: - name: G-19 CI anti-theater (run-gates execution proof required) if: always() env: - VT_G19_EXPECTED_RUN_GATES_SHA: "4a886170579c6b2eb6d910578b04ca42c9fa152209c4f902a950a68c0a274491" - VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "795dbee3bb62ade56dc6ba17868302d50cb887b152e1ec1a752b1f8bc2929b90" + VT_G19_EXPECTED_RUN_GATES_SHA: "71fc3804e355972a39564cce6dbef88c5ee749b7c6c5ace9921cbf923846cf25" + VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "4a863d7bf214e87a2def62e014ff9894cd4e65e826f0fd30f87edaf50c849489" VT_G19_EXPECTED_WORKSPACE_HELPER_SHA: "ecc05ac6690df69f5d1db7986bea397368fef247f2145c5965401d7b97384e31" VT_G19_EXPECTED_VERIFY_PY_SHA: "abedd126eac0227dddad9267d8863d1b3b4921ef0bdc69301f59b9c9d708683b" VT_G19_EXPECTED_VERIFY_SH_SHA: "65694b5e0abe95f3322c45ab7c69b597d83121ec14001bdbba8e6e83f2224192" - VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "d3efcdd8c4a612097a30aa3c98d3862cdfe78fe9fef5510568bea9fe49bb23b4" + VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "94c7a55ffcdd4b86cabf1b3fcd6ebb57d8250caadebe707f36b62d1dc6288483" VT_G19_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} VT_G19_PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before || github.sha }} VT_G19_CHECKOUT_SHA: ${{ github.sha }} diff --git a/tests/fixtures/g19-v2/mutant-prestep-colon-redir-run-gates.yml b/tests/fixtures/g19-v2/mutant-prestep-colon-redir-run-gates.yml new file mode 100644 index 0000000..bff290b --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-prestep-colon-redir-run-gates.yml @@ -0,0 +1,24 @@ +name: Verification Tools CI +jobs: + gates: + steps: + - name: Colon redirect run-gates + run: | + : > tests/run-gates.sh + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi \ No newline at end of file diff --git a/tests/fixtures/g19-v2/mutant-prestep-env-git-checkout-head-drift.yml b/tests/fixtures/g19-v2/mutant-prestep-env-git-checkout-head-drift.yml new file mode 100644 index 0000000..ded43d1 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-prestep-env-git-checkout-head-drift.yml @@ -0,0 +1,24 @@ +name: Verification Tools CI +jobs: + gates: + steps: + - name: Move git checkout before gate + run: | + env git checkout HEAD~1 + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi \ No newline at end of file diff --git a/tests/fixtures/g19-v2/mutant-prestep-git-C-checkout-head-drift.yml b/tests/fixtures/g19-v2/mutant-prestep-git-C-checkout-head-drift.yml new file mode 100644 index 0000000..8c78152 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-prestep-git-C-checkout-head-drift.yml @@ -0,0 +1,24 @@ +name: Verification Tools CI +jobs: + gates: + steps: + - name: Move git checkout before gate + run: | + git -C . checkout HEAD~1 + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi \ No newline at end of file diff --git a/tests/fixtures/g19-v2/mutant-prestep-tee-run-gates.yml b/tests/fixtures/g19-v2/mutant-prestep-tee-run-gates.yml new file mode 100644 index 0000000..bc14c40 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-prestep-tee-run-gates.yml @@ -0,0 +1,24 @@ +name: Verification Tools CI +jobs: + gates: + steps: + - name: Tee overwrite run-gates + run: | + tee tests/run-gates.sh /tmp/pwned + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi \ No newline at end of file diff --git a/tests/fixtures/g19-v2/mutant-sentinel-exec-printf-side-effect.yml b/tests/fixtures/g19-v2/mutant-sentinel-exec-printf-side-effect.yml new file mode 100644 index 0000000..1905768 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-sentinel-exec-printf-side-effect.yml @@ -0,0 +1,22 @@ +name: Verification Tools CI +jobs: + gates: + steps: + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + exec printf pwned > /tmp/pwned + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi \ No newline at end of file diff --git a/tests/g19-v2-structural-check.sh b/tests/g19-v2-structural-check.sh index 03f540d..2c01e35 100644 --- a/tests/g19-v2-structural-check.sh +++ b/tests/g19-v2-structural-check.sh @@ -800,12 +800,17 @@ def forbidden_environment_mutation(step): ) for protected_path in protected_paths: if protected_path in raw_code or protected_path in code: - if re.search(r'(^|[;&|]\s*)(cp|mv|install|truncate|dd)\b', code): + if re.search(r'(^|[;&|]\s*)(cp|mv|install|truncate|dd|tee)\b', code): return "tracked gate file" - if re.search(r'(^|[;&|]\s*)(cat|printf|echo|tee|python[0-9.]*|node|perl|ruby|sed)\b', code) and re.search(r'(>>?|--in-place|-i\b)', code): + if re.search(r'(^|[;&|]\s*)(cat|printf|echo|python[0-9.]*|node|perl|ruby|sed)\b', code) and re.search(r'(>>?|--in-place|-i\b)', code): + return "tracked gate file" + if re.search(r'(^|[;&|]\s*):?\s*>{1,2}', raw_code): return "tracked gate file" if step["name"] not in {GATE_STEP_NAME, SENTINEL_STEP_NAME}: - if re.search(r'(^|[;&|]\s*)(?:(?:command|builtin)\s+)?git\s+(checkout|switch|reset|clean|restore|update-ref|worktree|clone|fetch|pull|merge|rebase|submodule|apply|am|commit|add|rm|mv)\b', code): + if re.search( + r'(^|[;&|]\s*)(?:(?:env|command|builtin)\s+)*(?:/usr/bin/|/bin/)?git(?:\s+-C\s+\S+)*\s+(checkout|switch|reset|clean|restore|update-ref|worktree|clone|fetch|pull|merge|rebase|submodule|apply|am|commit|add|rm|mv)\b', + code, + ): return "git checkout state" if "$(" in raw_code or "`" in raw_code: return "computed environment file" @@ -1076,7 +1081,10 @@ if top_jobs_index is not None: if any(line in {"true", ":", "exit 0"} for line in sentinel_exec): fail("sentinel contains inert success command") if not strict_workflow_proof and any( - re.search(r'(^|[;&|]\s*)(touch|cp|mv|rm|printf|echo|python[0-9.]*|node|perl|ruby|sed|cat|tee|bash|sh|git)\b', line) + re.search( + r'(^|[;&|]\s*)(?:(?:exec|builtin|command)\s+)*(?:/usr/bin/|/bin/)?(touch|cp|mv|rm|printf|echo|python[0-9.]*|node|perl|ruby|sed|cat|tee|bash|sh|git)\b', + line, + ) for line in sentinel_exec ): fail("sentinel contains side-effecting command outside the proof contract") diff --git a/tests/run-gates.sh b/tests/run-gates.sh index 1c490cd..9e2bc2a 100644 --- a/tests/run-gates.sh +++ b/tests/run-gates.sh @@ -256,7 +256,7 @@ ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-alia ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|baseline-run-folded2-safe|baseline-run-pipe2-safe|mutant-prestep-run-folded2-line2-github-env|mutant-prestep-run-folded9-chomp-github-output-proof|mutant-prestep-run-folded9-line2-github-output-proof|mutant-prestep-run-pipe2-chomp-github-env|mutant-prestep-run-pipe2-line2-bashenv|mutant-prestep-run-pipe2-line2-github-env|mutant-prestep-run-pipe-chomp2-github-env|mutant-prestep-run-pipe9-line2-github-output-proof}" ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-prestep-list-item-merge-poison-run|mutant-prestep-list-item-merge-poison-env|mutant-prestep-list-item-merge-poison-uses|mutant-prestep-block-ansi-u005f-env|mutant-prestep-block-ansi-x5f-env|mutant-prestep-block-ansi-u005f-output-proof|mutant-prestep-block-ansi-u005f-path|mutant-sentinel-declare-hex-n-nameref|mutant-sentinel-local-hex-n-nameref|mutant-sentinel-typeset-hex-n-nameref|mutant-sentinel-declare-ansi-n-nameref|mutant-prestep-usrbin-bash-c-github-env|mutant-prestep-env-i-bash-c-github-output-proof|mutant-prestep-sh-c-github-env|mutant-prestep-tab-indented-block-github-env}" ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-inline-anonymous-run-pipe2-chomp-github-env|mutant-inline-run-plain-continuation-github-env-bashenv|mutant-inline-run-plain-continuation-github-output-proof|mutant-inline-run-single-quoted-continuation-github-env-bashenv|mutant-inline-run-single-quoted-continuation-github-output-proof}" -ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|baseline-no-working-directory-safe|mutant-prestep-shadow-run-gates-stub|mutant-prestep-shadow-structural-checker-stub|mutant-prestep-shadow-verifier-stub|mutant-step-working-directory-shadow-tree|mutant-job-working-directory-shadow-tree|mutant-prestep-block-ansi-octal-137-env|mutant-prestep-block-ansi-octal-137-output-proof|mutant-prestep-block-ansi-octal-137-path|mutant-prestep-block-ansi-octal-bashenv|mutant-prestep-source-github-env|mutant-prestep-dot-github-env|mutant-inline-run-single-quoted-doubled-quote-github-env|mutant-sentinel-proof-variable-option-plain-nameref-overwrite|mutant-prestep-command-substitution-bash-c-github-env|mutant-prestep-bash-ec-github-env|mutant-prestep-process-substitution-bash-github-env|mutant-prestep-git-checkout-head-drift|mutant-gate-post-verify-run-gates-rewrite|mutant-gate-missing-ci-yml-verify|mutant-gate-missing-helper-source|mutant-sentinel-extra-command|mutant-sentinel-v3-proof-preimage|mutant-workflow-dispatch-enabled|mutant-workflow-dispatch-flow-sequence|mutant-prestep-command-git-checkout-head-drift|mutant-sentinel-printf-redirect-side-effect|mutant-prestep-truncate-run-gates|mutant-gate-inline-git-checkout-before-run|mutant-sentinel-expected-proof-overwrite}" +ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|baseline-no-working-directory-safe|mutant-prestep-shadow-run-gates-stub|mutant-prestep-shadow-structural-checker-stub|mutant-prestep-shadow-verifier-stub|mutant-step-working-directory-shadow-tree|mutant-job-working-directory-shadow-tree|mutant-prestep-block-ansi-octal-137-env|mutant-prestep-block-ansi-octal-137-output-proof|mutant-prestep-block-ansi-octal-137-path|mutant-prestep-block-ansi-octal-bashenv|mutant-prestep-source-github-env|mutant-prestep-dot-github-env|mutant-inline-run-single-quoted-doubled-quote-github-env|mutant-sentinel-proof-variable-option-plain-nameref-overwrite|mutant-prestep-command-substitution-bash-c-github-env|mutant-prestep-bash-ec-github-env|mutant-prestep-process-substitution-bash-github-env|mutant-prestep-git-checkout-head-drift|mutant-gate-post-verify-run-gates-rewrite|mutant-gate-missing-ci-yml-verify|mutant-gate-missing-helper-source|mutant-sentinel-extra-command|mutant-sentinel-v3-proof-preimage|mutant-workflow-dispatch-enabled|mutant-workflow-dispatch-flow-sequence|mutant-prestep-command-git-checkout-head-drift|mutant-sentinel-printf-redirect-side-effect|mutant-prestep-truncate-run-gates|mutant-gate-inline-git-checkout-before-run|mutant-prestep-env-git-checkout-head-drift|mutant-prestep-usrbin-git-checkout-head-drift|mutant-prestep-git-C-checkout-head-drift|mutant-prestep-tee-run-gates|mutant-prestep-colon-redir-run-gates|mutant-sentinel-exec-printf-side-effect|mutant-sentinel-builtin-printf-side-effect|mutant-sentinel-expected-proof-overwrite}" TRACKED_FILES="$WORK/tracked-files.txt" if ! git ls-files >"$TRACKED_FILES"; then fail "G-18 tracked file scan completed" "git ls-files failed" @@ -433,6 +433,13 @@ mutant-prestep-command-git-checkout-head-drift.yml mutant-sentinel-printf-redirect-side-effect.yml mutant-prestep-truncate-run-gates.yml mutant-gate-inline-git-checkout-before-run.yml +mutant-prestep-env-git-checkout-head-drift.yml +mutant-prestep-usrbin-git-checkout-head-drift.yml +mutant-prestep-git-C-checkout-head-drift.yml +mutant-prestep-tee-run-gates.yml +mutant-prestep-colon-redir-run-gates.yml +mutant-sentinel-exec-printf-side-effect.yml +mutant-sentinel-builtin-printf-side-effect.yml mutant-sentinel-expected-proof-overwrite.yml mutant-sentinel-proof-variable-option-plain-nameref-overwrite.yml mutant-step-working-directory-shadow-tree.yml @@ -604,16 +611,21 @@ if [ "$FAILED" -eq 0 ]; then exit 2 fi done - EXECUTED_HEAD_SHA="$(git rev-parse HEAD)" + GIT_BIN="$(command -v git)" + if [ -z "$GIT_BIN" ] || [ ! -x "$GIT_BIN" ]; then + echo "SETUP FAIL: git executable is unavailable for proof binding" + exit 2 + fi + EXECUTED_HEAD_SHA="$("$GIT_BIN" rev-parse HEAD)" if [ "$PROOF_CHECKOUT_SHA" != "$EXECUTED_HEAD_SHA" ]; then echo "SETUP FAIL: VT_G19_CHECKOUT_SHA does not match executed worktree HEAD ($EXECUTED_HEAD_SHA)" exit 2 fi tracked_blob_sha256() { - git cat-file blob "$PROOF_CHECKOUT_SHA:$1" | sha256sum | awk '{print $1}' + "$GIT_BIN" cat-file blob "$PROOF_CHECKOUT_SHA:$1" | sha256sum | awk '{print $1}' } fixture_manifest_sha256() { - git ls-files 'tests/fixtures/g19-v2/*.yml' | + "$GIT_BIN" ls-files 'tests/fixtures/g19-v2/*.yml' | while IFS= read -r fixture; do printf '%s %s\n' "$(tracked_blob_sha256 "$fixture")" "$fixture" done | From 328e60b2541123d9e08124e40f1a309375efcd16 Mon Sep 17 00:00:00 2001 From: rene-founder Date: Tue, 7 Jul 2026 09:29:18 -0600 Subject: [PATCH 3/9] fix(g19): harden trusted git proof execution --- .github/workflows/ci.yml | 4 +- tests/lib/verify-tracked-workspace.sh | 19 +- tests/run-gates.sh | 310 ++++++++++++++++++++++++-- 3 files changed, 302 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88bad51..225e05b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,9 +100,9 @@ jobs: - name: G-19 CI anti-theater (run-gates execution proof required) if: always() env: - VT_G19_EXPECTED_RUN_GATES_SHA: "71fc3804e355972a39564cce6dbef88c5ee749b7c6c5ace9921cbf923846cf25" + VT_G19_EXPECTED_RUN_GATES_SHA: "167a4a69a611c3c0c900d58b66c1f91bcad2a12c30ebd790bbc3bf9ec58f39d6" VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "4a863d7bf214e87a2def62e014ff9894cd4e65e826f0fd30f87edaf50c849489" - VT_G19_EXPECTED_WORKSPACE_HELPER_SHA: "ecc05ac6690df69f5d1db7986bea397368fef247f2145c5965401d7b97384e31" + VT_G19_EXPECTED_WORKSPACE_HELPER_SHA: "e105de0bca00c509bfff6923515cccb9ab416013a20f17ff90a7159c52890a06" VT_G19_EXPECTED_VERIFY_PY_SHA: "abedd126eac0227dddad9267d8863d1b3b4921ef0bdc69301f59b9c9d708683b" VT_G19_EXPECTED_VERIFY_SH_SHA: "65694b5e0abe95f3322c45ab7c69b597d83121ec14001bdbba8e6e83f2224192" VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "94c7a55ffcdd4b86cabf1b3fcd6ebb57d8250caadebe707f36b62d1dc6288483" diff --git a/tests/lib/verify-tracked-workspace.sh b/tests/lib/verify-tracked-workspace.sh index 3cb2b33..bf880e9 100644 --- a/tests/lib/verify-tracked-workspace.sh +++ b/tests/lib/verify-tracked-workspace.sh @@ -8,9 +8,10 @@ verify_tracked_workspace_file() { exit 1 fi + git_bin="${GIT_BIN:-git}" checkout_sha="${VT_G19_CHECKOUT_SHA:-}" if [ -z "$checkout_sha" ]; then - checkout_sha="$(git rev-parse HEAD)" + checkout_sha="$("$git_bin" rev-parse HEAD)" fi if ! printf '%s\n' "$checkout_sha" | grep -qE '^[0-9a-f]{40}$'; then echo "G-19 FAIL: checkout SHA is invalid for workspace verification ($checkout_sha)" @@ -21,10 +22,18 @@ verify_tracked_workspace_file() { exit 1 fi - disk_sha="$(sha256sum "$path" | awk '{print $1}')" - blob_sha="$(git cat-file blob "$checkout_sha:$path" | sha256sum | awk '{print $1}')" - if [ "$disk_sha" != "$blob_sha" ]; then - echo "G-19 FAIL: workspace file differs from committed blob ($path)" + if ! "$git_bin" cat-file -e "$checkout_sha:$path" 2>/dev/null; then + echo "G-19 FAIL: tracked workspace blob is missing ($checkout_sha:$path)" + exit 1 + fi + if ! "$git_bin" diff --cached --quiet -- "$path"; then + echo "G-19 FAIL: staged workspace file differs from committed blob ($path)" + exit 1 + fi + expected_blob_id="$("$git_bin" rev-parse "$checkout_sha:$path")" + worktree_blob_id="$("$git_bin" hash-object --path="$path" "$path")" + if [ "$worktree_blob_id" != "$expected_blob_id" ]; then + echo "G-19 FAIL: workspace file differs from committed blob after Git checkout filters ($path)" exit 1 fi } diff --git a/tests/run-gates.sh b/tests/run-gates.sh index 9e2bc2a..c26b675 100644 --- a/tests/run-gates.sh +++ b/tests/run-gates.sh @@ -20,6 +20,187 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT="$(cd "$SCRIPT_DIR/.." && (cygpath -m "$(pwd)" 2>/dev/null || pwd -W 2>/dev/null || pwd))" cd "$ROOT" +reject_git_environment_poisoning() { + for name in \ + GIT_DIR \ + GIT_WORK_TREE \ + GIT_EXEC_PATH \ + GIT_INDEX_FILE \ + GIT_OBJECT_DIRECTORY \ + GIT_ALTERNATE_OBJECT_DIRECTORIES \ + GIT_CONFIG \ + GIT_CONFIG_GLOBAL \ + GIT_CONFIG_SYSTEM \ + GIT_CONFIG_NOSYSTEM \ + GIT_TRACE \ + GIT_TRACE_PACKET \ + GIT_TRACE_SETUP \ + GIT_TRACE_PERFORMANCE \ + GIT_SSH \ + GIT_SSH_COMMAND + do + eval "value=\${$name:-}" + if [ -n "$value" ]; then + echo "SETUP FAIL: proof-critical git environment variable is set ($name)" >&2 + exit 2 + fi + done + + git_config_env="$(env | sed -n 's/^\(GIT_CONFIG_[^=]*\)=.*/\1/p' | head -n 1)" + if [ -n "$git_config_env" ]; then + echo "SETUP FAIL: proof-critical git config environment variable is set ($git_config_env)" >&2 + exit 2 + fi +} + +gitdir_from_metadata() { + if [ -d "$ROOT/.git" ]; then + (cd "$ROOT/.git" && pwd -P) + return + fi + if [ ! -f "$ROOT/.git" ]; then + return 1 + fi + gitdir_line="$(sed -n '1p' "$ROOT/.git")" + case "$gitdir_line" in + gitdir:\ *) gitdir_path="${gitdir_line#gitdir: }" ;; + *) return 1 ;; + esac + case "$gitdir_path" in + /*|[A-Za-z]:/*|[A-Za-z]:\\*) ;; + *) gitdir_path="$ROOT/$gitdir_path" ;; + esac + (cd "$gitdir_path" && pwd -P) +} + +head_sha_from_metadata() { + gitdir="$(gitdir_from_metadata)" || return 1 + common_gitdir="$gitdir" + if [ -f "$gitdir/commondir" ]; then + common_gitdir_path="$(sed -n '1p' "$gitdir/commondir" | tr -d '\r\n')" + case "$common_gitdir_path" in + /*|[A-Za-z]:/*|[A-Za-z]:\\*) ;; + *) common_gitdir_path="$gitdir/$common_gitdir_path" ;; + esac + common_gitdir="$(cd "$common_gitdir_path" && pwd -P)" + fi + [ -f "$gitdir/HEAD" ] || return 1 + head_line="$(sed -n '1p' "$gitdir/HEAD" | tr -d '\r\n')" + case "$head_line" in + ref:\ *) + ref_name="${head_line#ref: }" + if [ -f "$gitdir/$ref_name" ]; then + sed -n '1p' "$gitdir/$ref_name" | tr -d '\r\n' + elif [ -f "$common_gitdir/$ref_name" ]; then + sed -n '1p' "$common_gitdir/$ref_name" | tr -d '\r\n' + elif [ -f "$gitdir/packed-refs" ]; then + awk -v ref="$ref_name" '$2 == ref { print $1; found = 1 } END { exit found ? 0 : 1 }' "$gitdir/packed-refs" + elif [ -f "$common_gitdir/packed-refs" ]; then + awk -v ref="$ref_name" '$2 == ref { print $1; found = 1 } END { exit found ? 0 : 1 }' "$common_gitdir/packed-refs" + else + return 1 + fi + ;; + *) printf '%s\n' "$head_line" ;; + esac +} + +is_trusted_git_path() { + candidate="$1" + case "$candidate" in + /usr/bin/git|/bin/git|/usr/local/bin/git|\ + /mingw64/bin/git|/mingw64/bin/git.exe|\ + /cmd/git|/cmd/git.exe|\ + /cygdrive/c/Program\ Files/Git/cmd/git|\ + /cygdrive/c/Program\ Files/Git/cmd/git.exe|\ + /cygdrive/c/Program\ Files/Git/bin/git|\ + /cygdrive/c/Program\ Files/Git/bin/git.exe|\ + /cygdrive/c/Program\ Files/Git/mingw64/bin/git|\ + /cygdrive/c/Program\ Files/Git/mingw64/bin/git.exe) + return 0 + ;; + esac + return 1 +} + +resolve_trusted_git() { + reject_git_environment_poisoning + candidate="$(command -v git || true)" + if [ -z "$candidate" ] || [ ! -x "$candidate" ]; then + echo "SETUP FAIL: git executable is unavailable" >&2 + exit 2 + fi + candidate_dir="$(cd "$(dirname "$candidate")" && pwd -P)" + candidate="$candidate_dir/$(basename "$candidate")" + if ! is_trusted_git_path "$candidate"; then + echo "SETUP FAIL: git executable resolves outside trusted system paths ($candidate)" >&2 + exit 2 + fi + manual_head="$(head_sha_from_metadata || true)" + if ! printf '%s\n' "$manual_head" | grep -qE '^[0-9a-f]{40}$'; then + echo "SETUP FAIL: could not resolve repository HEAD from git metadata" >&2 + exit 2 + fi + git_version="$("$candidate" --version 2>/dev/null || true)" + case "$git_version" in + git\ version\ *) ;; + *) + echo "SETUP FAIL: git executable version is incoherent ($git_version)" >&2 + exit 2 + ;; + esac + manual_gitdir="$(gitdir_from_metadata || true)" + candidate_gitdir="$("$candidate" rev-parse --git-dir 2>/dev/null || true)" + case "$candidate_gitdir" in + /*|[A-Za-z]:/*|[A-Za-z]:\\*) ;; + *) candidate_gitdir="$ROOT/$candidate_gitdir" ;; + esac + candidate_gitdir="$(cd "$candidate_gitdir" 2>/dev/null && pwd -P || true)" + if [ -z "$candidate_gitdir" ] || [ "$candidate_gitdir" != "$manual_gitdir" ]; then + echo "SETUP FAIL: git executable git-dir mismatch (metadata $manual_gitdir, git $candidate_gitdir)" >&2 + exit 2 + fi + git_head="$("$candidate" rev-parse HEAD 2>/dev/null || true)" + if [ "$git_head" != "$manual_head" ]; then + echo "SETUP FAIL: git executable HEAD mismatch (metadata $manual_head, git $git_head)" >&2 + exit 2 + fi + git_root="$("$candidate" rev-parse --show-toplevel 2>/dev/null || true)" + git_root="$(cd "$git_root" 2>/dev/null && (cygpath -m "$(pwd)" 2>/dev/null || pwd -W 2>/dev/null || pwd) || true)" + if [ "$git_root" != "$ROOT" ]; then + echo "SETUP FAIL: git executable root mismatch (expected $ROOT, got $git_root)" >&2 + exit 2 + fi + for required_blob in \ + .github/workflows/ci.yml \ + tests/run-gates.sh \ + tests/g19-v2-structural-check.sh \ + tests/lib/verify-tracked-workspace.sh + do + if ! "$candidate" cat-file -e "$manual_head:$required_blob" 2>/dev/null; then + echo "SETUP FAIL: git executable cannot read proof-bound object ($manual_head:$required_blob)" >&2 + exit 2 + fi + done + printf '%s\n' "$candidate" +} + +GIT_BIN="$(resolve_trusted_git)" || exit 2 + +assert_trusted_git_stable() { + current="$(command -v git || true)" + if [ -z "$current" ]; then + echo "SETUP FAIL: git executable disappeared from PATH before proof emission" >&2 + exit 2 + fi + current_dir="$(cd "$(dirname "$current")" && pwd -P)" + current="$current_dir/$(basename "$current")" + if [ "$current" != "$GIT_BIN" ]; then + echo "SETUP FAIL: git executable changed during proof run (was $GIT_BIN, now $current)" >&2 + exit 2 + fi +} + PY="$(command -v python3 || true)" [ -z "$PY" ] && PY="$(command -v python || true)" [ -z "$PY" ] && { echo "SETUP FAIL: no python3/python interpreter on PATH"; exit 2; } @@ -200,7 +381,7 @@ FORBIDDEN='certif(y|ied|ication|ications?)|compliant|compliance|court[- ]admissi G15_TARGETS=(README.md DISCLAIMER.md SECURITY.md memoriaia verify) G15_MISSING=0 for target in "${G15_TARGETS[@]}"; do - if ! git ls-files --error-unmatch "$target" >/dev/null 2>&1 && ! git ls-files "$target" | grep -q .; then + if ! "$GIT_BIN" ls-files --error-unmatch "$target" >/dev/null 2>&1 && ! "$GIT_BIN" ls-files "$target" | grep -q .; then fail "G-15 scan target exists" "$target is missing from tracked public proof surface" G15_MISSING=1 fi @@ -258,7 +439,7 @@ ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-pres ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-inline-anonymous-run-pipe2-chomp-github-env|mutant-inline-run-plain-continuation-github-env-bashenv|mutant-inline-run-plain-continuation-github-output-proof|mutant-inline-run-single-quoted-continuation-github-env-bashenv|mutant-inline-run-single-quoted-continuation-github-output-proof}" ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|baseline-no-working-directory-safe|mutant-prestep-shadow-run-gates-stub|mutant-prestep-shadow-structural-checker-stub|mutant-prestep-shadow-verifier-stub|mutant-step-working-directory-shadow-tree|mutant-job-working-directory-shadow-tree|mutant-prestep-block-ansi-octal-137-env|mutant-prestep-block-ansi-octal-137-output-proof|mutant-prestep-block-ansi-octal-137-path|mutant-prestep-block-ansi-octal-bashenv|mutant-prestep-source-github-env|mutant-prestep-dot-github-env|mutant-inline-run-single-quoted-doubled-quote-github-env|mutant-sentinel-proof-variable-option-plain-nameref-overwrite|mutant-prestep-command-substitution-bash-c-github-env|mutant-prestep-bash-ec-github-env|mutant-prestep-process-substitution-bash-github-env|mutant-prestep-git-checkout-head-drift|mutant-gate-post-verify-run-gates-rewrite|mutant-gate-missing-ci-yml-verify|mutant-gate-missing-helper-source|mutant-sentinel-extra-command|mutant-sentinel-v3-proof-preimage|mutant-workflow-dispatch-enabled|mutant-workflow-dispatch-flow-sequence|mutant-prestep-command-git-checkout-head-drift|mutant-sentinel-printf-redirect-side-effect|mutant-prestep-truncate-run-gates|mutant-gate-inline-git-checkout-before-run|mutant-prestep-env-git-checkout-head-drift|mutant-prestep-usrbin-git-checkout-head-drift|mutant-prestep-git-C-checkout-head-drift|mutant-prestep-tee-run-gates|mutant-prestep-colon-redir-run-gates|mutant-sentinel-exec-printf-side-effect|mutant-sentinel-builtin-printf-side-effect|mutant-sentinel-expected-proof-overwrite}" TRACKED_FILES="$WORK/tracked-files.txt" -if ! git ls-files >"$TRACKED_FILES"; then +if ! "$GIT_BIN" ls-files >"$TRACKED_FILES"; then fail "G-18 tracked file scan completed" "git ls-files failed" fi UNEXPECTED_STATUS=0 @@ -296,12 +477,63 @@ echo "[ci anti-theater]" bash tests/g19-v2-structural-check.sh .github/workflows/ci.yml || fail "G-19 v2 structural check" "ci.yml contains structural anomalies" if [ "$FAILED" -eq 0 ]; then pass "G-19 v2 structural check"; fi +FAKE_GIT_DIR="$WORK/fake-git-bin" +mkdir -p "$FAKE_GIT_DIR" +FAKE_GIT_HEAD="$(head_sha_from_metadata)" +cat > "$FAKE_GIT_DIR/git" <<'SH' +#!/usr/bin/env bash +case "$1 $2" in + "--version ") printf '%s\n' "git version 9.99.fake" ;; + "rev-parse HEAD") printf '%s\n' "__FAKE_GIT_HEAD__" ;; + "rev-parse --git-dir") printf '%s\n' ".git" ;; + "rev-parse --show-toplevel") pwd ;; + "cat-file -e") exit 0 ;; + "cat-file blob") printf '%s\n' "fake blob payload" ;; + *) exit 0 ;; +esac +SH +sed -i "s/__FAKE_GIT_HEAD__/$FAKE_GIT_HEAD/g" "$FAKE_GIT_DIR/git" +chmod +x "$FAKE_GIT_DIR/git" +if ( PATH="$FAKE_GIT_DIR:$PATH"; resolve_trusted_git ) >"$WORK/fake-git-resolve.out" 2>&1; then + fail "G-19 trusted git rejects PATH shadow" "fake git was accepted from $FAKE_GIT_DIR" +else + pass "G-19 trusted git rejects PATH shadow" +fi + +for poison_name in \ + GIT_EXEC_PATH \ + GIT_DIR \ + GIT_WORK_TREE \ + GIT_INDEX_FILE \ + GIT_OBJECT_DIRECTORY \ + GIT_ALTERNATE_OBJECT_DIRECTORIES \ + GIT_CONFIG \ + GIT_CONFIG_GLOBAL \ + GIT_CONFIG_SYSTEM \ + GIT_CONFIG_NOSYSTEM \ + GIT_TRACE \ + GIT_TRACE_PACKET \ + GIT_TRACE_SETUP \ + GIT_TRACE_PERFORMANCE \ + GIT_SSH \ + GIT_SSH_COMMAND +do + if ( + export "$poison_name=$WORK/poison" + reject_git_environment_poisoning + ) >"$WORK/git-env-poison-$poison_name.out" 2>&1; then + fail "G-19 git env poisoning rejects $poison_name" "$poison_name unexpectedly passed" + else + pass "G-19 git env poisoning rejects $poison_name" + fi +done + HELPER_WORKTREE="$WORK/helper-worktree" rm -rf "$HELPER_WORKTREE" -if git worktree add --detach "$HELPER_WORKTREE" "$(git rev-parse HEAD)" >/dev/null 2>&1; then +if "$GIT_BIN" worktree add --detach "$HELPER_WORKTREE" "$("$GIT_BIN" rev-parse HEAD)" >/dev/null 2>&1; then if ( cd "$HELPER_WORKTREE" && - VT_G19_CHECKOUT_SHA="$(git rev-parse HEAD)" && + VT_G19_CHECKOUT_SHA="$("$GIT_BIN" rev-parse HEAD)" && export VT_G19_CHECKOUT_SHA && . tests/lib/verify-tracked-workspace.sh && verify_tracked_workspace_file tests/run-gates.sh @@ -313,7 +545,7 @@ if git worktree add --detach "$HELPER_WORKTREE" "$(git rev-parse HEAD)" >/dev/nu if ( cd "$HELPER_WORKTREE" && printf '\n# tampered by helper self-test\n' >> tests/run-gates.sh && - VT_G19_CHECKOUT_SHA="$(git rev-parse HEAD)" && + VT_G19_CHECKOUT_SHA="$("$GIT_BIN" rev-parse HEAD)" && export VT_G19_CHECKOUT_SHA && . tests/lib/verify-tracked-workspace.sh && verify_tracked_workspace_file tests/run-gates.sh @@ -322,7 +554,18 @@ if git worktree add --detach "$HELPER_WORKTREE" "$(git rev-parse HEAD)" >/dev/nu else pass "G-19 tracked workspace helper rejects disk/blob drift" fi - git worktree remove --force "$HELPER_WORKTREE" >/dev/null 2>&1 || rm -rf "$HELPER_WORKTREE" + if ( + cd "$HELPER_WORKTREE" && + VT_G19_CHECKOUT_SHA="ffffffffffffffffffffffffffffffffffffffff" && + export VT_G19_CHECKOUT_SHA && + . tests/lib/verify-tracked-workspace.sh && + verify_tracked_workspace_file tests/run-gates.sh + ) >"$OUT" 2>&1; then + fail "G-19 tracked workspace helper rejects missing object" "missing blob unexpectedly passed" + else + pass "G-19 tracked workspace helper rejects missing object" + fi + "$GIT_BIN" worktree remove --force "$HELPER_WORKTREE" >/dev/null 2>&1 || rm -rf "$HELPER_WORKTREE" else fail "G-19 tracked workspace helper worktree setup" "git worktree add failed" rm -rf "$HELPER_WORKTREE" @@ -557,7 +800,7 @@ for fixture in $G19_EXPECTED_FIXTURES; do if [ ! -f "$fixture_path" ]; then fail "G-19 v2 fixture exists" "$fixture missing" G19_FIXTURE_MISSING=1 - elif ! git ls-files --error-unmatch "$fixture_path" >/dev/null 2>&1; then + elif ! "$GIT_BIN" ls-files --error-unmatch "$fixture_path" >/dev/null 2>&1; then fail "G-19 v2 fixture tracked" "$fixture is not tracked in git" G19_FIXTURE_MISSING=1 else @@ -601,7 +844,7 @@ fi echo if [ "$FAILED" -eq 0 ]; then echo "ALL GATES PASS" - HEAD_SHA="$(git rev-parse HEAD 2>/dev/null || printf 'unknown')" + HEAD_SHA="$("$GIT_BIN" rev-parse HEAD 2>/dev/null || printf 'unknown')" PROOF_PR_HEAD_SHA="${VT_G19_PR_HEAD_SHA:-$HEAD_SHA}" PROOF_PR_BASE_SHA="${VT_G19_PR_BASE_SHA:-$HEAD_SHA}" PROOF_CHECKOUT_SHA="${VT_G19_CHECKOUT_SHA:-$HEAD_SHA}" @@ -611,34 +854,53 @@ if [ "$FAILED" -eq 0 ]; then exit 2 fi done - GIT_BIN="$(command -v git)" - if [ -z "$GIT_BIN" ] || [ ! -x "$GIT_BIN" ]; then - echo "SETUP FAIL: git executable is unavailable for proof binding" - exit 2 - fi EXECUTED_HEAD_SHA="$("$GIT_BIN" rev-parse HEAD)" if [ "$PROOF_CHECKOUT_SHA" != "$EXECUTED_HEAD_SHA" ]; then echo "SETUP FAIL: VT_G19_CHECKOUT_SHA does not match executed worktree HEAD ($EXECUTED_HEAD_SHA)" exit 2 fi + assert_trusted_git_stable + VT_G19_CHECKOUT_SHA="$PROOF_CHECKOUT_SHA" + export VT_G19_CHECKOUT_SHA + . tests/lib/verify-tracked-workspace.sh + for proof_file in \ + .github/workflows/ci.yml \ + tests/run-gates.sh \ + tests/g19-v2-structural-check.sh \ + tests/lib/verify-tracked-workspace.sh \ + memoriaia/verify/verify-hashchain.py \ + verify/verify-hashchain.sh + do + verify_tracked_workspace_file "$proof_file" + done + "$GIT_BIN" ls-files 'tests/fixtures/g19-v2/*.yml' | + while IFS= read -r proof_fixture; do + verify_tracked_workspace_file "$proof_fixture" + done || exit 2 tracked_blob_sha256() { + if ! "$GIT_BIN" cat-file -e "$PROOF_CHECKOUT_SHA:$1" 2>/dev/null; then + echo "SETUP FAIL: proof-bound blob is missing ($PROOF_CHECKOUT_SHA:$1)" >&2 + return 2 + fi "$GIT_BIN" cat-file blob "$PROOF_CHECKOUT_SHA:$1" | sha256sum | awk '{print $1}' } fixture_manifest_sha256() { + fixture_manifest="$WORK/g19-fixture-manifest.txt" + : > "$fixture_manifest" "$GIT_BIN" ls-files 'tests/fixtures/g19-v2/*.yml' | while IFS= read -r fixture; do - printf '%s %s\n' "$(tracked_blob_sha256 "$fixture")" "$fixture" - done | - sha256sum | - awk '{print $1}' + fixture_sha="$(tracked_blob_sha256 "$fixture")" || exit 2 + printf '%s %s\n' "$fixture_sha" "$fixture" + done > "$fixture_manifest" || return 2 + sha256sum "$fixture_manifest" | awk '{print $1}' } - RUN_GATES_SHA="$(tracked_blob_sha256 "tests/run-gates.sh")" - STRUCTURAL_CHECK_SHA="$(tracked_blob_sha256 "tests/g19-v2-structural-check.sh")" - CI_YML_SHA="$(tracked_blob_sha256 ".github/workflows/ci.yml")" - WORKSPACE_HELPER_SHA="$(tracked_blob_sha256 "tests/lib/verify-tracked-workspace.sh")" - VERIFY_PY_SHA="$(tracked_blob_sha256 "memoriaia/verify/verify-hashchain.py")" - VERIFY_SH_SHA="$(tracked_blob_sha256 "verify/verify-hashchain.sh")" - G19_FIXTURE_MANIFEST_SHA="$(fixture_manifest_sha256)" + RUN_GATES_SHA="$(tracked_blob_sha256 "tests/run-gates.sh")" || exit 2 + STRUCTURAL_CHECK_SHA="$(tracked_blob_sha256 "tests/g19-v2-structural-check.sh")" || exit 2 + CI_YML_SHA="$(tracked_blob_sha256 ".github/workflows/ci.yml")" || exit 2 + WORKSPACE_HELPER_SHA="$(tracked_blob_sha256 "tests/lib/verify-tracked-workspace.sh")" || exit 2 + VERIFY_PY_SHA="$(tracked_blob_sha256 "memoriaia/verify/verify-hashchain.py")" || exit 2 + VERIFY_SH_SHA="$(tracked_blob_sha256 "verify/verify-hashchain.sh")" || exit 2 + G19_FIXTURE_MANIFEST_SHA="$(fixture_manifest_sha256)" || exit 2 VT_G19_EXEC_PROOF="$( printf 'VT_G19_EXECUTED:v4\nPR_HEAD:%s\nPR_BASE:%s\nCHECKOUT:%s\nCI_YML:%s\nRUN_GATES:%s\nSTRUCTURAL:%s\nWORKSPACE_HELPER:%s\nVERIFY_PY:%s\nVERIFY_SH:%s\nFIXTURES:%s\n' \ "$PROOF_PR_HEAD_SHA" "$PROOF_PR_BASE_SHA" "$PROOF_CHECKOUT_SHA" \ From 65a8529754846e8f05ac6a87c02f1b5992f3ad90 Mon Sep 17 00:00:00 2001 From: rene-founder Date: Tue, 7 Jul 2026 09:43:59 -0600 Subject: [PATCH 4/9] fix(g19): normalize trusted git root path --- .github/workflows/ci.yml | 2 +- tests/run-gates.sh | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 225e05b..f43b7ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,7 +100,7 @@ jobs: - name: G-19 CI anti-theater (run-gates execution proof required) if: always() env: - VT_G19_EXPECTED_RUN_GATES_SHA: "167a4a69a611c3c0c900d58b66c1f91bcad2a12c30ebd790bbc3bf9ec58f39d6" + VT_G19_EXPECTED_RUN_GATES_SHA: "0c3198c8eeaa6c294f0f4297ca353e2c4b38ad9d541b340fb8134278ea412e41" VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "4a863d7bf214e87a2def62e014ff9894cd4e65e826f0fd30f87edaf50c849489" VT_G19_EXPECTED_WORKSPACE_HELPER_SHA: "e105de0bca00c509bfff6923515cccb9ab416013a20f17ff90a7159c52890a06" VT_G19_EXPECTED_VERIFY_PY_SHA: "abedd126eac0227dddad9267d8863d1b3b4921ef0bdc69301f59b9c9d708683b" diff --git a/tests/run-gates.sh b/tests/run-gates.sh index c26b675..73baaef 100644 --- a/tests/run-gates.sh +++ b/tests/run-gates.sh @@ -19,6 +19,7 @@ set -u SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" ROOT="$(cd "$SCRIPT_DIR/.." && (cygpath -m "$(pwd)" 2>/dev/null || pwd -W 2>/dev/null || pwd))" cd "$ROOT" +ROOT_PHYSICAL="$(pwd -P)" reject_git_environment_poisoning() { for name in \ @@ -166,9 +167,9 @@ resolve_trusted_git() { exit 2 fi git_root="$("$candidate" rev-parse --show-toplevel 2>/dev/null || true)" - git_root="$(cd "$git_root" 2>/dev/null && (cygpath -m "$(pwd)" 2>/dev/null || pwd -W 2>/dev/null || pwd) || true)" - if [ "$git_root" != "$ROOT" ]; then - echo "SETUP FAIL: git executable root mismatch (expected $ROOT, got $git_root)" >&2 + git_root_physical="$(cd "$git_root" 2>/dev/null && pwd -P || true)" + if [ "$git_root_physical" != "$ROOT_PHYSICAL" ]; then + echo "SETUP FAIL: git executable root mismatch (expected $ROOT_PHYSICAL, got $git_root_physical)" >&2 exit 2 fi for required_blob in \ From f1bd9455086f5f91daa4b973846d9b384e4fad9d Mon Sep 17 00:00:00 2001 From: rene-founder Date: Tue, 7 Jul 2026 10:06:27 -0600 Subject: [PATCH 5/9] fix(g19): seal git environment trust boundary --- .github/workflows/ci.yml | 156 ++++++++++++++++-- .../g19-v2/mutant-job-env-git-work-tree.yml | 24 +++ .../mutant-step-env-git-object-directory.yml | 24 +++ .../g19-v2/mutant-workflow-env-git-dir.yml | 24 +++ tests/g19-v2-structural-check.sh | 52 ++++-- tests/run-gates.sh | 5 +- 6 files changed, 249 insertions(+), 36 deletions(-) create mode 100644 tests/fixtures/g19-v2/mutant-job-env-git-work-tree.yml create mode 100644 tests/fixtures/g19-v2/mutant-step-env-git-object-directory.yml create mode 100644 tests/fixtures/g19-v2/mutant-workflow-env-git-dir.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f43b7ce..87faca5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,27 +72,91 @@ jobs: VT_G19_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} VT_G19_PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before || github.sha }} VT_G19_CHECKOUT_SHA: ${{ github.sha }} + VT_G19_EXPECTED_WORKSPACE_HELPER_SHA: "e105de0bca00c509bfff6923515cccb9ab416013a20f17ff90a7159c52890a06" run: | + if [ -n "${GIT_DIR:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_DIR)" + exit 1 + fi + if [ -n "${GIT_WORK_TREE:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_WORK_TREE)" + exit 1 + fi + if [ -n "${GIT_EXEC_PATH:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_EXEC_PATH)" + exit 1 + fi + if [ -n "${GIT_INDEX_FILE:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_INDEX_FILE)" + exit 1 + fi + if [ -n "${GIT_OBJECT_DIRECTORY:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_OBJECT_DIRECTORY)" + exit 1 + fi + if [ -n "${GIT_ALTERNATE_OBJECT_DIRECTORIES:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_ALTERNATE_OBJECT_DIRECTORIES)" + exit 1 + fi + if [ -n "${GIT_CONFIG_GLOBAL:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG_GLOBAL)" + exit 1 + fi + if [ -n "${GIT_CONFIG_SYSTEM:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG_SYSTEM)" + exit 1 + fi + if [ -n "${GIT_CONFIG_NOSYSTEM:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG_NOSYSTEM)" + exit 1 + fi + git_config_env="$(env | sed -n 's/^\(GIT_CONFIG_[^=]*\)=.*/\1/p' | head -n 1)" + if [ -n "$git_config_env" ]; then + echo "G-19 FAIL: proof-critical git config environment variable is set ($git_config_env)" + exit 1 + fi + if ! GIT_BIN="$(command -v git)"; then + echo "G-19 FAIL: git executable is unavailable" + exit 1 + fi + if [ ! -x "$GIT_BIN" ]; then + echo "G-19 FAIL: git executable is unavailable" + exit 1 + fi + GIT_BIN_DIR="$(cd "$(dirname "$GIT_BIN")" && pwd -P)" + GIT_BIN="$GIT_BIN_DIR/$(basename "$GIT_BIN")" + WORKSPACE_PHYSICAL="$(pwd -P)" + if [[ "$GIT_BIN" == "$WORKSPACE_PHYSICAL"/* || "$GIT_BIN" == /tmp/* || "$GIT_BIN" == /var/tmp/* || "$GIT_BIN" == /dev/shm/* || "$GIT_BIN" == */AppData/Local/Temp/* || "$GIT_BIN" == */Temp/* || "$GIT_BIN" == */_temp/* ]]; then + echo "G-19 FAIL: git executable resolves outside trusted system paths ($GIT_BIN)" + exit 1 + fi ROOT_WORKSPACE="$PWD" MATERIALIZED_WORKTREE="$(mktemp -d)" cleanup_materialized_worktree() { cd "$ROOT_WORKSPACE" - git worktree remove --force "$MATERIALIZED_WORKTREE" >/dev/null 2>&1 || rm -rf "$MATERIALIZED_WORKTREE" + "$GIT_BIN" worktree remove --force "$MATERIALIZED_WORKTREE" >/dev/null 2>&1 || rm -rf "$MATERIALIZED_WORKTREE" } trap cleanup_materialized_worktree EXIT - git config core.autocrlf false - git worktree add --detach "$MATERIALIZED_WORKTREE" "$VT_G19_CHECKOUT_SHA" + "$GIT_BIN" config core.autocrlf false + "$GIT_BIN" worktree add --detach "$MATERIALIZED_WORKTREE" "$VT_G19_CHECKOUT_SHA" cd "$MATERIALIZED_WORKTREE" - git config core.autocrlf false - git reset --hard "$VT_G19_CHECKOUT_SHA" - source tests/lib/verify-tracked-workspace.sh + "$GIT_BIN" config core.autocrlf false + "$GIT_BIN" reset --hard "$VT_G19_CHECKOUT_SHA" + VERIFIED_WORKSPACE_HELPER="$MATERIALIZED_WORKTREE/.verified-workspace-helper" + "$GIT_BIN" cat-file blob "$VT_G19_CHECKOUT_SHA:tests/lib/verify-tracked-workspace.sh" > "$VERIFIED_WORKSPACE_HELPER" + WORKSPACE_HELPER_SHA="$(sha256sum "$VERIFIED_WORKSPACE_HELPER" | awk '{print $1}')" + if [ "$WORKSPACE_HELPER_SHA" != "$VT_G19_EXPECTED_WORKSPACE_HELPER_SHA" ]; then + echo "G-19 FAIL: verified workspace helper hash mismatch ($WORKSPACE_HELPER_SHA)" + exit 1 + fi + source "$VERIFIED_WORKSPACE_HELPER" verify_tracked_workspace_file .github/workflows/ci.yml verify_tracked_workspace_file tests/lib/verify-tracked-workspace.sh verify_tracked_workspace_file tests/run-gates.sh verify_tracked_workspace_file tests/g19-v2-structural-check.sh verify_tracked_workspace_file memoriaia/verify/verify-hashchain.py verify_tracked_workspace_file verify/verify-hashchain.sh - git ls-files 'tests/fixtures/g19-v2/*.yml' | while IFS= read -r fixture; do + "$GIT_BIN" ls-files 'tests/fixtures/g19-v2/*.yml' | while IFS= read -r fixture; do verify_tracked_workspace_file "$fixture" done bash tests/run-gates.sh @@ -100,16 +164,72 @@ jobs: - name: G-19 CI anti-theater (run-gates execution proof required) if: always() env: - VT_G19_EXPECTED_RUN_GATES_SHA: "0c3198c8eeaa6c294f0f4297ca353e2c4b38ad9d541b340fb8134278ea412e41" - VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "4a863d7bf214e87a2def62e014ff9894cd4e65e826f0fd30f87edaf50c849489" + VT_G19_EXPECTED_RUN_GATES_SHA: "c051591e9ddb0f39755669a8fcf0cfb274677b148d21f970db75eb6f294b95ee" + VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "974776bc43525ca66ebc4ccbc81e3bfc525dbdac771f922d67ffac9186f6a7ff" VT_G19_EXPECTED_WORKSPACE_HELPER_SHA: "e105de0bca00c509bfff6923515cccb9ab416013a20f17ff90a7159c52890a06" VT_G19_EXPECTED_VERIFY_PY_SHA: "abedd126eac0227dddad9267d8863d1b3b4921ef0bdc69301f59b9c9d708683b" VT_G19_EXPECTED_VERIFY_SH_SHA: "65694b5e0abe95f3322c45ab7c69b597d83121ec14001bdbba8e6e83f2224192" - VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "94c7a55ffcdd4b86cabf1b3fcd6ebb57d8250caadebe707f36b62d1dc6288483" + VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "6d570540f0abca4371324ee90121bc0c57e8ac82cb4527045f11f0d617b06d17" VT_G19_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} VT_G19_PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before || github.sha }} VT_G19_CHECKOUT_SHA: ${{ github.sha }} run: | + if [ -n "${GIT_DIR:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_DIR)" + exit 1 + fi + if [ -n "${GIT_WORK_TREE:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_WORK_TREE)" + exit 1 + fi + if [ -n "${GIT_EXEC_PATH:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_EXEC_PATH)" + exit 1 + fi + if [ -n "${GIT_INDEX_FILE:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_INDEX_FILE)" + exit 1 + fi + if [ -n "${GIT_OBJECT_DIRECTORY:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_OBJECT_DIRECTORY)" + exit 1 + fi + if [ -n "${GIT_ALTERNATE_OBJECT_DIRECTORIES:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_ALTERNATE_OBJECT_DIRECTORIES)" + exit 1 + fi + if [ -n "${GIT_CONFIG_GLOBAL:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG_GLOBAL)" + exit 1 + fi + if [ -n "${GIT_CONFIG_SYSTEM:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG_SYSTEM)" + exit 1 + fi + if [ -n "${GIT_CONFIG_NOSYSTEM:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG_NOSYSTEM)" + exit 1 + fi + git_config_env="$(env | sed -n 's/^\(GIT_CONFIG_[^=]*\)=.*/\1/p' | head -n 1)" + if [ -n "$git_config_env" ]; then + echo "G-19 FAIL: proof-critical git config environment variable is set ($git_config_env)" + exit 1 + fi + if ! GIT_BIN="$(command -v git)"; then + echo "G-19 FAIL: git executable is unavailable" + exit 1 + fi + if [ ! -x "$GIT_BIN" ]; then + echo "G-19 FAIL: git executable is unavailable" + exit 1 + fi + GIT_BIN_DIR="$(cd "$(dirname "$GIT_BIN")" && pwd -P)" + GIT_BIN="$GIT_BIN_DIR/$(basename "$GIT_BIN")" + WORKSPACE_PHYSICAL="$(pwd -P)" + if [[ "$GIT_BIN" == "$WORKSPACE_PHYSICAL"/* || "$GIT_BIN" == /tmp/* || "$GIT_BIN" == /var/tmp/* || "$GIT_BIN" == /dev/shm/* || "$GIT_BIN" == */AppData/Local/Temp/* || "$GIT_BIN" == */Temp/* || "$GIT_BIN" == */_temp/* ]]; then + echo "G-19 FAIL: git executable resolves outside trusted system paths ($GIT_BIN)" + exit 1 + fi if [ "${{ steps.run_gates.outcome }}" != "success" ]; then echo "G-19 FAIL: tests/run-gates.sh did not complete successfully (outcome: ${{ steps.run_gates.outcome }})" exit 1 @@ -126,38 +246,38 @@ jobs: echo "G-19 FAIL: checkout SHA is invalid ($VT_G19_CHECKOUT_SHA)" exit 1 fi - CHECKOUT_SHA="$(git rev-parse HEAD)" + CHECKOUT_SHA="$("$GIT_BIN" rev-parse HEAD)" if [ "$CHECKOUT_SHA" != "$VT_G19_CHECKOUT_SHA" ]; then echo "G-19 FAIL: checkout SHA mismatch (expected $VT_G19_CHECKOUT_SHA, got $CHECKOUT_SHA)" exit 1 fi - RUN_GATES_SHA="$(git cat-file blob "$CHECKOUT_SHA:tests/run-gates.sh" | sha256sum | awk '{print $1}')" + RUN_GATES_SHA="$("$GIT_BIN" cat-file blob "$CHECKOUT_SHA:tests/run-gates.sh" | sha256sum | awk '{print $1}')" if [ "$RUN_GATES_SHA" != "$VT_G19_EXPECTED_RUN_GATES_SHA" ]; then echo "G-19 FAIL: tests/run-gates.sh hash mismatch ($RUN_GATES_SHA)" exit 1 fi - STRUCTURAL_CHECK_SHA="$(git cat-file blob "$CHECKOUT_SHA:tests/g19-v2-structural-check.sh" | sha256sum | awk '{print $1}')" + STRUCTURAL_CHECK_SHA="$("$GIT_BIN" cat-file blob "$CHECKOUT_SHA:tests/g19-v2-structural-check.sh" | sha256sum | awk '{print $1}')" if [ "$STRUCTURAL_CHECK_SHA" != "$VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA" ]; then echo "G-19 FAIL: tests/g19-v2-structural-check.sh hash mismatch ($STRUCTURAL_CHECK_SHA)" exit 1 fi - WORKSPACE_HELPER_SHA="$(git cat-file blob "$CHECKOUT_SHA:tests/lib/verify-tracked-workspace.sh" | sha256sum | awk '{print $1}')" + WORKSPACE_HELPER_SHA="$("$GIT_BIN" cat-file blob "$CHECKOUT_SHA:tests/lib/verify-tracked-workspace.sh" | sha256sum | awk '{print $1}')" if [ "$WORKSPACE_HELPER_SHA" != "$VT_G19_EXPECTED_WORKSPACE_HELPER_SHA" ]; then echo "G-19 FAIL: tests/lib/verify-tracked-workspace.sh hash mismatch ($WORKSPACE_HELPER_SHA)" exit 1 fi - CI_YML_SHA="$(git cat-file blob "$CHECKOUT_SHA:.github/workflows/ci.yml" | sha256sum | awk '{print $1}')" - VERIFY_PY_SHA="$(git cat-file blob "$CHECKOUT_SHA:memoriaia/verify/verify-hashchain.py" | sha256sum | awk '{print $1}')" + CI_YML_SHA="$("$GIT_BIN" cat-file blob "$CHECKOUT_SHA:.github/workflows/ci.yml" | sha256sum | awk '{print $1}')" + VERIFY_PY_SHA="$("$GIT_BIN" cat-file blob "$CHECKOUT_SHA:memoriaia/verify/verify-hashchain.py" | sha256sum | awk '{print $1}')" if [ "$VERIFY_PY_SHA" != "$VT_G19_EXPECTED_VERIFY_PY_SHA" ]; then echo "G-19 FAIL: memoriaia/verify/verify-hashchain.py hash mismatch ($VERIFY_PY_SHA)" exit 1 fi - VERIFY_SH_SHA="$(git cat-file blob "$CHECKOUT_SHA:verify/verify-hashchain.sh" | sha256sum | awk '{print $1}')" + VERIFY_SH_SHA="$("$GIT_BIN" cat-file blob "$CHECKOUT_SHA:verify/verify-hashchain.sh" | sha256sum | awk '{print $1}')" if [ "$VERIFY_SH_SHA" != "$VT_G19_EXPECTED_VERIFY_SH_SHA" ]; then echo "G-19 FAIL: verify/verify-hashchain.sh hash mismatch ($VERIFY_SH_SHA)" exit 1 fi - FIXTURE_MANIFEST_SHA="$(git ls-files 'tests/fixtures/g19-v2/*.yml' | while IFS= read -r fixture; do fixture_sha="$(git cat-file blob "$CHECKOUT_SHA:$fixture" | sha256sum | awk '{print $1}')"; printf '%s %s\n' "$fixture_sha" "$fixture"; done | sha256sum | awk '{print $1}')" + FIXTURE_MANIFEST_SHA="$("$GIT_BIN" ls-files 'tests/fixtures/g19-v2/*.yml' | while IFS= read -r fixture; do fixture_sha="$("$GIT_BIN" cat-file blob "$CHECKOUT_SHA:$fixture" | sha256sum | awk '{print $1}')"; printf '%s %s\n' "$fixture_sha" "$fixture"; done | sha256sum | awk '{print $1}')" if [ "$FIXTURE_MANIFEST_SHA" != "$VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA" ]; then echo "G-19 FAIL: G-19 fixture manifest hash mismatch ($FIXTURE_MANIFEST_SHA)" exit 1 diff --git a/tests/fixtures/g19-v2/mutant-job-env-git-work-tree.yml b/tests/fixtures/g19-v2/mutant-job-env-git-work-tree.yml new file mode 100644 index 0000000..29f17df --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-job-env-git-work-tree.yml @@ -0,0 +1,24 @@ +name: mutant job env GIT_WORK_TREE +on: [pull_request] +jobs: + gates: + runs-on: ubuntu-latest + env: + GIT_WORK_TREE: /tmp/poison-worktree + steps: + - name: Run verification gate suite + id: run_gates + run: bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi diff --git a/tests/fixtures/g19-v2/mutant-step-env-git-object-directory.yml b/tests/fixtures/g19-v2/mutant-step-env-git-object-directory.yml new file mode 100644 index 0000000..c437689 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-step-env-git-object-directory.yml @@ -0,0 +1,24 @@ +name: mutant step env GIT_OBJECT_DIRECTORY +on: [pull_request] +jobs: + gates: + runs-on: ubuntu-latest + steps: + - name: Run verification gate suite + id: run_gates + env: + GIT_OBJECT_DIRECTORY: /tmp/poison-objects + run: bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi diff --git a/tests/fixtures/g19-v2/mutant-workflow-env-git-dir.yml b/tests/fixtures/g19-v2/mutant-workflow-env-git-dir.yml new file mode 100644 index 0000000..ee25ba2 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-workflow-env-git-dir.yml @@ -0,0 +1,24 @@ +name: mutant workflow env GIT_DIR +on: [pull_request] +env: + GIT_DIR: /tmp/poison-git +jobs: + gates: + runs-on: ubuntu-latest + steps: + - name: Run verification gate suite + id: run_gates + run: bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi diff --git a/tests/g19-v2-structural-check.sh b/tests/g19-v2-structural-check.sh index 2c01e35..3b77619 100644 --- a/tests/g19-v2-structural-check.sh +++ b/tests/g19-v2-structural-check.sh @@ -20,6 +20,7 @@ else fi "$PYTHON_BIN" - "$WORKFLOW" <<'PY' +import hashlib import re import sys @@ -469,6 +470,10 @@ def executable_lines(run_lines): return result +def exec_digest(exec_lines): + return hashlib.sha256("\n".join(exec_lines).encode("utf-8")).hexdigest() + + def contains_neutralizer(text): return re.search(r'(^|\s)set\s+\+e(\s|$)|\|\|\s*(true|:)(\s|\)|;|$)|;\s*(true|exit\s+0)(\s|\)|;|$)', text) is not None @@ -696,7 +701,7 @@ def inspect_env_block(env_index, env_indent, context): if not parsed or parsed["indent"] != env_indent + 2: continue env_key = parsed["key"].upper() - if env_key in FORBIDDEN_ENV_KEYS: + if env_key in FORBIDDEN_ENV_KEYS or env_key.startswith("GIT_"): fail(f"{context}.env must not define {env_key}") if re.match(r'^BASH_FUNC_.*%%$', env_key): fail(f"{context}.env must not define Bash function export key {env_key}") @@ -821,6 +826,8 @@ def forbidden_environment_mutation(step): if re.search(r'(^|[^A-Za-z0-9_])eval\b', code): return "computed environment file" if re.search(r'(^|[;&|]\s*)\$\{?[A-Za-z_][A-Za-z0-9_]*\}?(?=\s|$)', code): + if re.search(r'(^|[;&|]\s*)\$\{?GIT_BIN\}?(\s|$)', code): + continue return "computed environment file" if re.search(r'(^|[;&|]\s*)env\b[^#;&|]*\s(?:/usr/bin/|/bin/)?(bash|sh|dash|zsh|ksh)\s+[^#;&|]*-[A-Za-z]*c[A-Za-z]*(\s|$)', code): return "computed environment file" @@ -834,6 +841,8 @@ def forbidden_environment_mutation(step): return "GITHUB_ENV" if "BASH_ENV" in code: return "BASH_ENV" + if re.search(r'(^|[\s;])(export\s+)?GIT_(DIR|WORK_TREE|EXEC_PATH|INDEX_FILE|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES|CONFIG[A-Za-z0-9_]*)=', code): + return "GIT_*" if re.search(r'(^|[\s;])(export\s+)?(BASH_ENV|PATH|PYTHON|PYTHONPATH)=', code): return "shell environment" if "GITHUB_PATH" in raw_code or "GITHUB_PATH" in code: @@ -957,7 +966,8 @@ if top_jobs_index is not None: fail("workflow must not write vt_g19_exec_proof directly through GITHUB_OUTPUT") env_mutation = forbidden_environment_mutation(step) if env_mutation: - fail(f"workflow must not mutate {env_mutation} in the gates job") + if step["name"] not in {GATE_STEP_NAME, SENTINEL_STEP_NAME} or env_mutation != "computed environment file": + fail(f"workflow must not mutate {env_mutation} in the gates job") gate_steps = [step for step in steps if step["name"] == GATE_STEP_NAME] sentinel_steps = [step for step in steps if step["name"] == SENTINEL_STEP_NAME] @@ -1012,17 +1022,20 @@ if top_jobs_index is not None: "done", "bash tests/run-gates.sh", ] - if gate_exec != expected_gate_exec: + allowed_gate_digests = { + "991cd5d874ac25b68142335282501e9746b6870480678a7536d38782ec63779e", + } + if gate_exec != expected_gate_exec and exec_digest(gate_exec) not in allowed_gate_digests: fail("gate execution step must match the exact strict proof command sequence") for snippet in ( 'ROOT_WORKSPACE="$PWD"', 'MATERIALIZED_WORKTREE="$(mktemp -d)"', - "git config core.autocrlf false", - 'git worktree add --detach "$MATERIALIZED_WORKTREE" "$VT_G19_CHECKOUT_SHA"', + "config core.autocrlf false", + 'worktree add --detach "$MATERIALIZED_WORKTREE" "$VT_G19_CHECKOUT_SHA"', 'cd "$MATERIALIZED_WORKTREE"', - "git config core.autocrlf false", - 'git reset --hard "$VT_G19_CHECKOUT_SHA"', - "source tests/lib/verify-tracked-workspace.sh", + "config core.autocrlf false", + 'reset --hard "$VT_G19_CHECKOUT_SHA"', + "source ", "verify_tracked_workspace_file .github/workflows/ci.yml", "verify_tracked_workspace_file tests/lib/verify-tracked-workspace.sh", "verify_tracked_workspace_file tests/run-gates.sh", @@ -1053,6 +1066,8 @@ if top_jobs_index is not None: for key, expected_value in expected_event_env.items(): if gate_env.get(key) != expected_value: fail(f"gate execution step env must define {key} from GitHub event context") + if exec_digest(gate_exec) in allowed_gate_digests and re.fullmatch(r'[0-9a-f]{64}', gate_env.get("VT_G19_EXPECTED_WORKSPACE_HELPER_SHA", "")) is None: + fail("gate execution step env must define VT_G19_EXPECTED_WORKSPACE_HELPER_SHA as a 64-hex hash") if len(sentinel_steps) == 1: sentinel = sentinel_steps[0] @@ -1190,7 +1205,10 @@ if top_jobs_index is not None: 'echo "G-19 PASS: PR head $VT_G19_PR_HEAD_SHA; base $VT_G19_PR_BASE_SHA; checkout $CHECKOUT_SHA"', 'echo "G-19 PASS: Execution proved ($PROOF)"', ] - if sentinel_exec != expected_sentinel_exec: + allowed_sentinel_digests = { + "1acf0ea25bda4e6a90dd5eec47abdaccf9fcd0f5f7d3550147ad39390d47c38e", + } + if sentinel_exec != expected_sentinel_exec and exec_digest(sentinel_exec) not in allowed_sentinel_digests: fail("sentinel step must match the exact strict proof command sequence") expected_proof_assignments = [line for line in sentinel_exec if re.match(r'^EXPECTED_PROOF=', line)] if len(expected_proof_assignments) != 1 or not expected_proof_assignments[0].startswith('EXPECTED_PROOF="$(printf '): @@ -1239,14 +1257,14 @@ if top_jobs_index is not None: if re.fullmatch(r'[0-9a-f]{64}', sentinel_env.get(key, "")) is None: fail(f"sentinel env must define {key} as a 64-hex hash") for snippet in ( - 'git cat-file blob "$CHECKOUT_SHA:tests/run-gates.sh"', - 'git cat-file blob "$CHECKOUT_SHA:tests/g19-v2-structural-check.sh"', - 'git cat-file blob "$CHECKOUT_SHA:tests/lib/verify-tracked-workspace.sh"', - 'git cat-file blob "$CHECKOUT_SHA:.github/workflows/ci.yml"', - 'git cat-file blob "$CHECKOUT_SHA:memoriaia/verify/verify-hashchain.py"', - 'git cat-file blob "$CHECKOUT_SHA:verify/verify-hashchain.sh"', - 'git ls-files \'tests/fixtures/g19-v2/*.yml\'', - 'git cat-file blob "$CHECKOUT_SHA:$fixture"', + 'cat-file blob "$CHECKOUT_SHA:tests/run-gates.sh"', + 'cat-file blob "$CHECKOUT_SHA:tests/g19-v2-structural-check.sh"', + 'cat-file blob "$CHECKOUT_SHA:tests/lib/verify-tracked-workspace.sh"', + 'cat-file blob "$CHECKOUT_SHA:.github/workflows/ci.yml"', + 'cat-file blob "$CHECKOUT_SHA:memoriaia/verify/verify-hashchain.py"', + 'cat-file blob "$CHECKOUT_SHA:verify/verify-hashchain.sh"', + 'ls-files \'tests/fixtures/g19-v2/*.yml\'', + 'cat-file blob "$CHECKOUT_SHA:$fixture"', ): if not any(snippet in line for line in sentinel_exec): fail(f"sentinel must compute proof material from Git blob input: {snippet}") diff --git a/tests/run-gates.sh b/tests/run-gates.sh index 73baaef..724665e 100644 --- a/tests/run-gates.sh +++ b/tests/run-gates.sh @@ -782,10 +782,13 @@ mutant-inline-anonymous-run-github-env-bashenv.yml mutant-inline-anonymous-run-github-output-proof.yml mutant-step-merge-key-continue-on-error.yml mutant-step-env-path-poison.yml +mutant-step-env-git-object-directory.yml mutant-step-quoted-continue-on-error.yml mutant-step-quoted-if-run.yml mutant-step-uses-upload-artifact.yml mutant-workflow-env-path-poison.yml +mutant-workflow-env-git-dir.yml +mutant-job-env-git-work-tree.yml mutant-workflow-default-shell-alias-or-true.yml mutant-workflow-default-shell-flow-map-or-true.yml mutant-workflow-default-shell-merge-key-or-true.yml @@ -844,7 +847,6 @@ fi echo if [ "$FAILED" -eq 0 ]; then - echo "ALL GATES PASS" HEAD_SHA="$("$GIT_BIN" rev-parse HEAD 2>/dev/null || printf 'unknown')" PROOF_PR_HEAD_SHA="${VT_G19_PR_HEAD_SHA:-$HEAD_SHA}" PROOF_PR_BASE_SHA="${VT_G19_PR_BASE_SHA:-$HEAD_SHA}" @@ -909,6 +911,7 @@ if [ "$FAILED" -eq 0 ]; then sha256sum | awk '{print $1}' )" + echo "ALL GATES PASS" echo "VT_G19_EXEC_PROOF=$VT_G19_EXEC_PROOF" [ -n "${GITHUB_OUTPUT:-}" ] && echo "vt_g19_exec_proof=$VT_G19_EXEC_PROOF" >> "$GITHUB_OUTPUT" exit 0 From a4ff6219160e69095ec37f5a1cd527b960781857 Mon Sep 17 00:00:00 2001 From: rene-founder Date: Tue, 7 Jul 2026 10:10:15 -0600 Subject: [PATCH 6/9] fix(g19): update git environment fixture inventory --- .github/workflows/ci.yml | 2 +- tests/run-gates.sh | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87faca5..d06126a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -164,7 +164,7 @@ jobs: - name: G-19 CI anti-theater (run-gates execution proof required) if: always() env: - VT_G19_EXPECTED_RUN_GATES_SHA: "c051591e9ddb0f39755669a8fcf0cfb274677b148d21f970db75eb6f294b95ee" + VT_G19_EXPECTED_RUN_GATES_SHA: "0e04fb1be0c1557d6899031b470136bde04fbbaaf8c977270e695a7d2be9e508" VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "974776bc43525ca66ebc4ccbc81e3bfc525dbdac771f922d67ffac9186f6a7ff" VT_G19_EXPECTED_WORKSPACE_HELPER_SHA: "e105de0bca00c509bfff6923515cccb9ab416013a20f17ff90a7159c52890a06" VT_G19_EXPECTED_VERIFY_PY_SHA: "abedd126eac0227dddad9267d8863d1b3b4921ef0bdc69301f59b9c9d708683b" diff --git a/tests/run-gates.sh b/tests/run-gates.sh index 724665e..25bd2ea 100644 --- a/tests/run-gates.sh +++ b/tests/run-gates.sh @@ -411,7 +411,7 @@ fi [ ! -f memoriaia/verify/requirements.txt ] && pass "G-17 no phantom requirements.txt" || fail "G-17 phantom requirements.txt" "unexpected requirements.txt present" # ---- G-18: no leakage — allowlist + sensitive-pattern denylist (hard fail) -ALLOWED='^(README\.md|SECURITY\.md|DISCLAIMER\.md|LICENSE|\.gitignore|\.gitattributes|\.github/workflows/ci\.yml|memoriaia/schema/[A-Za-z0-9._-]+\.sql|memoriaia/fixtures/[A-Za-z0-9._-]+\.sql|memoriaia/verify/verify-hashchain\.py|verify/verify-hashchain\.sh|tests/run-gates\.sh|tests/g19-v2-structural-check\.sh|tests/lib/verify-tracked-workspace\.sh|tests/fixtures/g19-v2/(baseline-good|baseline-unrelated-github-output|missing-proof-mutant|mutant-comment-only-sentinel|mutant-continue-on-error|mutant-direct-github-output-proof-write|mutant-folded-subshell-true-paren|mutant-forged-indirect-output-unreachable|mutant-forged-proof-output|mutant-gates-extraction-service-name-collision|mutant-gate-steps-hidden-in-shell-string|mutant-gates-needs-skipped-blocker|mutant-job-default-shell-alias-or-true|mutant-job-default-shell-flow-map-or-true|mutant-job-default-shell-merge-key-or-true|mutant-job-default-shell-or-true|mutant-job-default-shell-run-alias-or-true|mutant-if-false-run|mutant-job-continue-on-error|mutant-job-if-false|mutant-job-if-post-steps-expression|mutant-job-quoted-continue-on-error|mutant-job-quoted-if-false|mutant-job-yaml-alias-continue-on-error|mutant-job-yaml-alias-if-false|mutant-jobs-key-in-block-scalar|mutant-missing-sentinel|mutant-or-true-paren|mutant-or-true|mutant-prestep-bashenv-forged-output|mutant-prestep-github-path-python-poison|mutant-semicolon-true|mutant-sentinel-case-inert-guard|mutant-sentinel-echo-only-failure|mutant-sentinel-exit-in-else-branch|mutant-sentinel-false-and-brace-group|mutant-sentinel-heredoc-inert|mutant-sentinel-heredoc-numeric-delimiter|mutant-sentinel-exit-zero-expression|mutant-sentinel-fake-outcome-comparison|mutant-sentinel-invalid-proof-echo-branch|mutant-sentinel-invalid-proof-elif-exit|mutant-sentinel-invalid-proof-nested-inert-exit|mutant-sentinel-missing-proof-elif-exit|mutant-sentinel-missing-proof-nested-inert-exit|mutant-sentinel-outcome-elif-exit|mutant-sentinel-outcome-nested-inert-exit|mutant-sentinel-proof-array-overwrite|mutant-sentinel-proof-declare-overwrite|mutant-sentinel-proof-nameref-overwrite|mutant-sentinel-proof-parameter-default|mutant-sentinel-proof-overwrite-constant|mutant-sentinel-quoted-continue-on-error|mutant-sentinel-skipped-or-group|mutant-sentinel-split-line-function|mutant-sentinel-step-if-skipped|mutant-sentinel-trap-exit-zero|mutant-sentinel-uncalled-function|mutant-sentinel-unreachable-invalid-proof-guard|mutant-sentinel-unreachable-missing-proof-guard|mutant-sentinel-while-false-inert-guard|mutant-step-if-expression-run|mutant-step-quoted-continue-on-error|mutant-step-quoted-if-run|mutant-workflow-default-shell-alias-or-true|mutant-workflow-default-shell-flow-map-or-true|mutant-workflow-default-shell-merge-key-or-true|mutant-workflow-default-shell-or-true|mutant-workflow-default-shell-run-alias-or-true|skipped-run_gates-mutant)\.yml)$' +ALLOWED='^(README\.md|SECURITY\.md|DISCLAIMER\.md|LICENSE|\.gitignore|\.gitattributes|\.github/workflows/ci\.yml|memoriaia/schema/[A-Za-z0-9._-]+\.sql|memoriaia/fixtures/[A-Za-z0-9._-]+\.sql|memoriaia/verify/verify-hashchain\.py|verify/verify-hashchain\.sh|tests/run-gates\.sh|tests/g19-v2-structural-check\.sh|tests/lib/verify-tracked-workspace\.sh|tests/fixtures/g19-v2/(baseline-good|baseline-unrelated-github-output|missing-proof-mutant|mutant-comment-only-sentinel|mutant-continue-on-error|mutant-direct-github-output-proof-write|mutant-folded-subshell-true-paren|mutant-forged-indirect-output-unreachable|mutant-forged-proof-output|mutant-gates-extraction-service-name-collision|mutant-gate-steps-hidden-in-shell-string|mutant-gates-needs-skipped-blocker|mutant-job-default-shell-alias-or-true|mutant-job-default-shell-flow-map-or-true|mutant-job-default-shell-merge-key-or-true|mutant-job-default-shell-or-true|mutant-job-default-shell-run-alias-or-true|mutant-if-false-run|mutant-job-continue-on-error|mutant-job-env-git-work-tree|mutant-job-if-false|mutant-job-if-post-steps-expression|mutant-job-quoted-continue-on-error|mutant-job-quoted-if-false|mutant-job-yaml-alias-continue-on-error|mutant-job-yaml-alias-if-false|mutant-jobs-key-in-block-scalar|mutant-missing-sentinel|mutant-or-true-paren|mutant-or-true|mutant-prestep-bashenv-forged-output|mutant-prestep-github-path-python-poison|mutant-semicolon-true|mutant-sentinel-case-inert-guard|mutant-sentinel-echo-only-failure|mutant-sentinel-exit-in-else-branch|mutant-sentinel-false-and-brace-group|mutant-sentinel-heredoc-inert|mutant-sentinel-heredoc-numeric-delimiter|mutant-sentinel-exit-zero-expression|mutant-sentinel-fake-outcome-comparison|mutant-sentinel-invalid-proof-echo-branch|mutant-sentinel-invalid-proof-elif-exit|mutant-sentinel-invalid-proof-nested-inert-exit|mutant-sentinel-missing-proof-elif-exit|mutant-sentinel-missing-proof-nested-inert-exit|mutant-sentinel-outcome-elif-exit|mutant-sentinel-outcome-nested-inert-exit|mutant-sentinel-proof-array-overwrite|mutant-sentinel-proof-declare-overwrite|mutant-sentinel-proof-nameref-overwrite|mutant-sentinel-proof-parameter-default|mutant-sentinel-proof-overwrite-constant|mutant-sentinel-quoted-continue-on-error|mutant-sentinel-skipped-or-group|mutant-sentinel-split-line-function|mutant-sentinel-step-if-skipped|mutant-sentinel-trap-exit-zero|mutant-sentinel-uncalled-function|mutant-sentinel-unreachable-invalid-proof-guard|mutant-sentinel-unreachable-missing-proof-guard|mutant-sentinel-while-false-inert-guard|mutant-step-env-git-object-directory|mutant-step-if-expression-run|mutant-step-quoted-continue-on-error|mutant-step-quoted-if-run|mutant-workflow-default-shell-alias-or-true|mutant-workflow-default-shell-flow-map-or-true|mutant-workflow-default-shell-merge-key-or-true|mutant-workflow-default-shell-or-true|mutant-workflow-default-shell-run-alias-or-true|mutant-workflow-env-git-dir|skipped-run_gates-mutant)\.yml)$' ALLOWED="${ALLOWED/mutant-prestep-bashenv-forged-output|mutant-prestep-github-path-python-poison/mutant-job-env-bashenv-obfuscated-output|mutant-prestep-bashenv-forged-output|mutant-prestep-github-path-chocolatey-poison|mutant-prestep-github-path-python-poison|mutant-prestep-heredoc-github-output-proof-write|mutant-prestep-indirect-github-output-proof-write}" ALLOWED="${ALLOWED/mutant-job-env-bashenv-obfuscated-output/mutant-gates-merge-key-bypass|mutant-job-env-bashenv-obfuscated-output}" ALLOWED="${ALLOWED/mutant-prestep-indirect-github-output-proof-write/mutant-prestep-computed-github-output-proof-write|mutant-prestep-indirect-github-output-proof-write|mutant-prestep-obfuscated-env-poison}" @@ -439,6 +439,7 @@ ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|baseline-ru ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-prestep-list-item-merge-poison-run|mutant-prestep-list-item-merge-poison-env|mutant-prestep-list-item-merge-poison-uses|mutant-prestep-block-ansi-u005f-env|mutant-prestep-block-ansi-x5f-env|mutant-prestep-block-ansi-u005f-output-proof|mutant-prestep-block-ansi-u005f-path|mutant-sentinel-declare-hex-n-nameref|mutant-sentinel-local-hex-n-nameref|mutant-sentinel-typeset-hex-n-nameref|mutant-sentinel-declare-ansi-n-nameref|mutant-prestep-usrbin-bash-c-github-env|mutant-prestep-env-i-bash-c-github-output-proof|mutant-prestep-sh-c-github-env|mutant-prestep-tab-indented-block-github-env}" ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-inline-anonymous-run-pipe2-chomp-github-env|mutant-inline-run-plain-continuation-github-env-bashenv|mutant-inline-run-plain-continuation-github-output-proof|mutant-inline-run-single-quoted-continuation-github-env-bashenv|mutant-inline-run-single-quoted-continuation-github-output-proof}" ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|baseline-no-working-directory-safe|mutant-prestep-shadow-run-gates-stub|mutant-prestep-shadow-structural-checker-stub|mutant-prestep-shadow-verifier-stub|mutant-step-working-directory-shadow-tree|mutant-job-working-directory-shadow-tree|mutant-prestep-block-ansi-octal-137-env|mutant-prestep-block-ansi-octal-137-output-proof|mutant-prestep-block-ansi-octal-137-path|mutant-prestep-block-ansi-octal-bashenv|mutant-prestep-source-github-env|mutant-prestep-dot-github-env|mutant-inline-run-single-quoted-doubled-quote-github-env|mutant-sentinel-proof-variable-option-plain-nameref-overwrite|mutant-prestep-command-substitution-bash-c-github-env|mutant-prestep-bash-ec-github-env|mutant-prestep-process-substitution-bash-github-env|mutant-prestep-git-checkout-head-drift|mutant-gate-post-verify-run-gates-rewrite|mutant-gate-missing-ci-yml-verify|mutant-gate-missing-helper-source|mutant-sentinel-extra-command|mutant-sentinel-v3-proof-preimage|mutant-workflow-dispatch-enabled|mutant-workflow-dispatch-flow-sequence|mutant-prestep-command-git-checkout-head-drift|mutant-sentinel-printf-redirect-side-effect|mutant-prestep-truncate-run-gates|mutant-gate-inline-git-checkout-before-run|mutant-prestep-env-git-checkout-head-drift|mutant-prestep-usrbin-git-checkout-head-drift|mutant-prestep-git-C-checkout-head-drift|mutant-prestep-tee-run-gates|mutant-prestep-colon-redir-run-gates|mutant-sentinel-exec-printf-side-effect|mutant-sentinel-builtin-printf-side-effect|mutant-sentinel-expected-proof-overwrite}" +ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-workflow-env-git-dir|mutant-job-env-git-work-tree|mutant-step-env-git-object-directory}" TRACKED_FILES="$WORK/tracked-files.txt" if ! "$GIT_BIN" ls-files >"$TRACKED_FILES"; then fail "G-18 tracked file scan completed" "git ls-files failed" @@ -911,9 +912,9 @@ if [ "$FAILED" -eq 0 ]; then sha256sum | awk '{print $1}' )" - echo "ALL GATES PASS" echo "VT_G19_EXEC_PROOF=$VT_G19_EXEC_PROOF" [ -n "${GITHUB_OUTPUT:-}" ] && echo "vt_g19_exec_proof=$VT_G19_EXEC_PROOF" >> "$GITHUB_OUTPUT" + echo "ALL GATES PASS" exit 0 else echo "GATE FAILURE(S) DETECTED" From 521b9096645a6735770305b944112e495e87f830 Mon Sep 17 00:00:00 2001 From: rene-founder Date: Tue, 7 Jul 2026 10:28:30 -0600 Subject: [PATCH 7/9] fix(g19): align fixture manifest pin --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d06126a..da55d48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -169,7 +169,7 @@ jobs: VT_G19_EXPECTED_WORKSPACE_HELPER_SHA: "e105de0bca00c509bfff6923515cccb9ab416013a20f17ff90a7159c52890a06" VT_G19_EXPECTED_VERIFY_PY_SHA: "abedd126eac0227dddad9267d8863d1b3b4921ef0bdc69301f59b9c9d708683b" VT_G19_EXPECTED_VERIFY_SH_SHA: "65694b5e0abe95f3322c45ab7c69b597d83121ec14001bdbba8e6e83f2224192" - VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "6d570540f0abca4371324ee90121bc0c57e8ac82cb4527045f11f0d617b06d17" + VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "02d1b4e074ecc70c99b266aebede1c4773a6c5397945d9bf3a9bb79dba107d44" VT_G19_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} VT_G19_PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before || github.sha }} VT_G19_CHECKOUT_SHA: ${{ github.sha }} From 79cc81d93a7c86a0cb31c2427c11889dd338f3b6 Mon Sep 17 00:00:00 2001 From: rene-founder Date: Tue, 7 Jul 2026 11:01:11 -0600 Subject: [PATCH 8/9] fix(g19): harden PR15 proof boundary --- .github/workflows/ci.yml | 30 +++++++++++-- ...mutant-job-env-git-ceiling-directories.yml | 23 ++++++++++ .../mutant-prestep-var-run-gates-writer.yml | 25 +++++++++++ .../mutant-prestep-write-trusted-git.yml | 24 ++++++++++ ...utant-workflow-dispatch-block-sequence.yml | 24 ++++++++++ .../mutant-workflow-env-git-namespace.yml | 23 ++++++++++ tests/g19-v2-structural-check.sh | 44 ++++++++++++++++--- tests/run-gates.sh | 19 +++++--- 8 files changed, 197 insertions(+), 15 deletions(-) create mode 100644 tests/fixtures/g19-v2/mutant-job-env-git-ceiling-directories.yml create mode 100644 tests/fixtures/g19-v2/mutant-prestep-var-run-gates-writer.yml create mode 100644 tests/fixtures/g19-v2/mutant-prestep-write-trusted-git.yml create mode 100644 tests/fixtures/g19-v2/mutant-workflow-dispatch-block-sequence.yml create mode 100644 tests/fixtures/g19-v2/mutant-workflow-env-git-namespace.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da55d48..e795b81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,6 +98,10 @@ jobs: echo "G-19 FAIL: proof-critical git environment variable is set (GIT_ALTERNATE_OBJECT_DIRECTORIES)" exit 1 fi + if [ -n "${GIT_CONFIG:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG)" + exit 1 + fi if [ -n "${GIT_CONFIG_GLOBAL:-}" ]; then echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG_GLOBAL)" exit 1 @@ -110,6 +114,14 @@ jobs: echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG_NOSYSTEM)" exit 1 fi + if [ -n "${GIT_NAMESPACE:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_NAMESPACE)" + exit 1 + fi + if [ -n "${GIT_CEILING_DIRECTORIES:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CEILING_DIRECTORIES)" + exit 1 + fi git_config_env="$(env | sed -n 's/^\(GIT_CONFIG_[^=]*\)=.*/\1/p' | head -n 1)" if [ -n "$git_config_env" ]; then echo "G-19 FAIL: proof-critical git config environment variable is set ($git_config_env)" @@ -164,12 +176,12 @@ jobs: - name: G-19 CI anti-theater (run-gates execution proof required) if: always() env: - VT_G19_EXPECTED_RUN_GATES_SHA: "0e04fb1be0c1557d6899031b470136bde04fbbaaf8c977270e695a7d2be9e508" - VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "974776bc43525ca66ebc4ccbc81e3bfc525dbdac771f922d67ffac9186f6a7ff" + VT_G19_EXPECTED_RUN_GATES_SHA: "af56651d70f46dfcf124bed4e41632565be87eacbd123ba13a7348ad99d15283" + VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "db8e425f0f23914c37bcfb4f99a05eaf7c1e5ff75ff78dd0e9eeb8c651258232" VT_G19_EXPECTED_WORKSPACE_HELPER_SHA: "e105de0bca00c509bfff6923515cccb9ab416013a20f17ff90a7159c52890a06" VT_G19_EXPECTED_VERIFY_PY_SHA: "abedd126eac0227dddad9267d8863d1b3b4921ef0bdc69301f59b9c9d708683b" VT_G19_EXPECTED_VERIFY_SH_SHA: "65694b5e0abe95f3322c45ab7c69b597d83121ec14001bdbba8e6e83f2224192" - VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "02d1b4e074ecc70c99b266aebede1c4773a6c5397945d9bf3a9bb79dba107d44" + VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "ba4df165f7fa9a5738f5e9d128e6dd935e1ea2ea2a9f5cb6d3b0872faf5f3c3e" VT_G19_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} VT_G19_PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before || github.sha }} VT_G19_CHECKOUT_SHA: ${{ github.sha }} @@ -198,6 +210,10 @@ jobs: echo "G-19 FAIL: proof-critical git environment variable is set (GIT_ALTERNATE_OBJECT_DIRECTORIES)" exit 1 fi + if [ -n "${GIT_CONFIG:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG)" + exit 1 + fi if [ -n "${GIT_CONFIG_GLOBAL:-}" ]; then echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG_GLOBAL)" exit 1 @@ -210,6 +226,14 @@ jobs: echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CONFIG_NOSYSTEM)" exit 1 fi + if [ -n "${GIT_NAMESPACE:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_NAMESPACE)" + exit 1 + fi + if [ -n "${GIT_CEILING_DIRECTORIES:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CEILING_DIRECTORIES)" + exit 1 + fi git_config_env="$(env | sed -n 's/^\(GIT_CONFIG_[^=]*\)=.*/\1/p' | head -n 1)" if [ -n "$git_config_env" ]; then echo "G-19 FAIL: proof-critical git config environment variable is set ($git_config_env)" diff --git a/tests/fixtures/g19-v2/mutant-job-env-git-ceiling-directories.yml b/tests/fixtures/g19-v2/mutant-job-env-git-ceiling-directories.yml new file mode 100644 index 0000000..960530d --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-job-env-git-ceiling-directories.yml @@ -0,0 +1,23 @@ +name: Verification Tools CI +jobs: + gates: + env: + GIT_CEILING_DIRECTORIES: / + steps: + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi diff --git a/tests/fixtures/g19-v2/mutant-prestep-var-run-gates-writer.yml b/tests/fixtures/g19-v2/mutant-prestep-var-run-gates-writer.yml new file mode 100644 index 0000000..04334ac --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-prestep-var-run-gates-writer.yml @@ -0,0 +1,25 @@ +name: Verification Tools CI +jobs: + gates: + steps: + - name: Indirect run-gates writer + run: | + TARGET=tests/run-gates.sh + printf 'pwned\n' > "$TARGET" + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi diff --git a/tests/fixtures/g19-v2/mutant-prestep-write-trusted-git.yml b/tests/fixtures/g19-v2/mutant-prestep-write-trusted-git.yml new file mode 100644 index 0000000..21fdf77 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-prestep-write-trusted-git.yml @@ -0,0 +1,24 @@ +name: Verification Tools CI +jobs: + gates: + steps: + - name: Rewrite trusted git binary path + run: | + printf 'pwned\n' > /usr/local/bin/git + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi diff --git a/tests/fixtures/g19-v2/mutant-workflow-dispatch-block-sequence.yml b/tests/fixtures/g19-v2/mutant-workflow-dispatch-block-sequence.yml new file mode 100644 index 0000000..e6c8eff --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-workflow-dispatch-block-sequence.yml @@ -0,0 +1,24 @@ +name: Verification Tools CI +on: + - pull_request + - workflow_dispatch +jobs: + gates: + steps: + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi diff --git a/tests/fixtures/g19-v2/mutant-workflow-env-git-namespace.yml b/tests/fixtures/g19-v2/mutant-workflow-env-git-namespace.yml new file mode 100644 index 0000000..09cbaa5 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-workflow-env-git-namespace.yml @@ -0,0 +1,23 @@ +name: Verification Tools CI +env: + GIT_NAMESPACE: poison +jobs: + gates: + steps: + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi diff --git a/tests/g19-v2-structural-check.sh b/tests/g19-v2-structural-check.sh index 3b77619..9831f11 100644 --- a/tests/g19-v2-structural-check.sh +++ b/tests/g19-v2-structural-check.sh @@ -228,6 +228,12 @@ for index, line in enumerate(lines): on_value = strip_quotes(parsed["value"]) if re.search(r"(^|[,\[\s])workflow_dispatch([,\]\s]|$)", on_value): fail("workflow_dispatch must not be enabled for the required G-19 proof workflow") + on_end = block_end(index, parsed["indent"], SCALAR_LINES) + for child_index in range(index + 1, on_end): + if SCALAR_LINES[child_index]: + continue + if re.match(r'^\s*-\s*workflow_dispatch\s*(?:#.*)?$', lines[child_index]): + fail("workflow_dispatch must not be enabled for the required G-19 proof workflow") if len(top_jobs) != 1: fail(f"expected exactly one top-level jobs: key, found {len(top_jobs)}") @@ -790,6 +796,17 @@ def allowed_github_path_write(step, code): def forbidden_environment_mutation(step): raw_code_lines = [line.split("#", 1)[0].strip() for line in executable_lines(step["run_lines"])] code_lines = [normalize_shell_words(line) for line in raw_code_lines] + path_aliases = set() + + def writer_command_or_redirect(raw_code, code): + if re.search(r'(^|[;&|]\s*)(cp|mv|install|truncate|dd|tee)\b', code): + return True + if re.search(r'(^|[;&|]\s*)(cat|printf|echo|python[0-9.]*|node|perl|ruby|sed)\b', code) and re.search(r'(>>?|--in-place|-i\b)', code): + return True + if re.search(r'(^|[;&|]\s*):?\s*>{1,2}', raw_code): + return True + return False + for line in executable_lines(step["run_lines"]): if "\t" in line: return "tab-indented shell text" @@ -802,15 +819,26 @@ def forbidden_environment_mutation(step): "tests/lib/verify-tracked-workspace.sh", "memoriaia/verify/verify-hashchain.py", "verify/verify-hashchain.sh", + "/usr/bin/git", + "/bin/git", + "/usr/local/bin/git", + "/usr/bin", + "/bin", + "/usr/local/bin", ) + assignment = re.match(r'^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$', code) + if assignment: + for protected_path in protected_paths: + if protected_path in assignment.group(2): + path_aliases.add(assignment.group(1)) for protected_path in protected_paths: if protected_path in raw_code or protected_path in code: - if re.search(r'(^|[;&|]\s*)(cp|mv|install|truncate|dd|tee)\b', code): - return "tracked gate file" - if re.search(r'(^|[;&|]\s*)(cat|printf|echo|python[0-9.]*|node|perl|ruby|sed)\b', code) and re.search(r'(>>?|--in-place|-i\b)', code): - return "tracked gate file" - if re.search(r'(^|[;&|]\s*):?\s*>{1,2}', raw_code): + if writer_command_or_redirect(raw_code, code): return "tracked gate file" + for alias in path_aliases: + alias_pattern = r'\$\{?' + re.escape(alias) + r'\}?(?=\W|$)' + if re.search(alias_pattern, code) and writer_command_or_redirect(raw_code, code): + return "tracked gate file" if step["name"] not in {GATE_STEP_NAME, SENTINEL_STEP_NAME}: if re.search( r'(^|[;&|]\s*)(?:(?:env|command|builtin)\s+)*(?:/usr/bin/|/bin/)?git(?:\s+-C\s+\S+)*\s+(checkout|switch|reset|clean|restore|update-ref|worktree|clone|fetch|pull|merge|rebase|submodule|apply|am|commit|add|rm|mv)\b', @@ -1023,7 +1051,8 @@ if top_jobs_index is not None: "bash tests/run-gates.sh", ] allowed_gate_digests = { - "991cd5d874ac25b68142335282501e9746b6870480678a7536d38782ec63779e", + "1a047f357bfa20490e35bf61f4d32c271b286cd40b3430acc20ebb17222e631f", + "8c7e04e0a7acfc3877047e5e4154c3ceda231891e59c484b778051d961f77ff9", } if gate_exec != expected_gate_exec and exec_digest(gate_exec) not in allowed_gate_digests: fail("gate execution step must match the exact strict proof command sequence") @@ -1206,7 +1235,8 @@ if top_jobs_index is not None: 'echo "G-19 PASS: Execution proved ($PROOF)"', ] allowed_sentinel_digests = { - "1acf0ea25bda4e6a90dd5eec47abdaccf9fcd0f5f7d3550147ad39390d47c38e", + "e31864ca935eb2778bcae7f07bfd049524b280a7f6b29a30bf148ba5b94961da", + "d511d151880b1e4cb9a7b8cd62c760a2cbc21528855fe28c151c217755aa2c3c", } if sentinel_exec != expected_sentinel_exec and exec_digest(sentinel_exec) not in allowed_sentinel_digests: fail("sentinel step must match the exact strict proof command sequence") diff --git a/tests/run-gates.sh b/tests/run-gates.sh index 25bd2ea..6d17779 100644 --- a/tests/run-gates.sh +++ b/tests/run-gates.sh @@ -38,10 +38,11 @@ reject_git_environment_poisoning() { GIT_TRACE_SETUP \ GIT_TRACE_PERFORMANCE \ GIT_SSH \ - GIT_SSH_COMMAND + GIT_SSH_COMMAND \ + GIT_NAMESPACE \ + GIT_CEILING_DIRECTORIES do - eval "value=\${$name:-}" - if [ -n "$value" ]; then + if printenv "$name" >/dev/null 2>&1; then echo "SETUP FAIL: proof-critical git environment variable is set ($name)" >&2 exit 2 fi @@ -109,7 +110,7 @@ head_sha_from_metadata() { is_trusted_git_path() { candidate="$1" case "$candidate" in - /usr/bin/git|/bin/git|/usr/local/bin/git|\ + /usr/bin/git|/bin/git|\ /mingw64/bin/git|/mingw64/bin/git.exe|\ /cmd/git|/cmd/git.exe|\ /cygdrive/c/Program\ Files/Git/cmd/git|\ @@ -440,6 +441,7 @@ ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-pres ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-inline-anonymous-run-pipe2-chomp-github-env|mutant-inline-run-plain-continuation-github-env-bashenv|mutant-inline-run-plain-continuation-github-output-proof|mutant-inline-run-single-quoted-continuation-github-env-bashenv|mutant-inline-run-single-quoted-continuation-github-output-proof}" ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|baseline-no-working-directory-safe|mutant-prestep-shadow-run-gates-stub|mutant-prestep-shadow-structural-checker-stub|mutant-prestep-shadow-verifier-stub|mutant-step-working-directory-shadow-tree|mutant-job-working-directory-shadow-tree|mutant-prestep-block-ansi-octal-137-env|mutant-prestep-block-ansi-octal-137-output-proof|mutant-prestep-block-ansi-octal-137-path|mutant-prestep-block-ansi-octal-bashenv|mutant-prestep-source-github-env|mutant-prestep-dot-github-env|mutant-inline-run-single-quoted-doubled-quote-github-env|mutant-sentinel-proof-variable-option-plain-nameref-overwrite|mutant-prestep-command-substitution-bash-c-github-env|mutant-prestep-bash-ec-github-env|mutant-prestep-process-substitution-bash-github-env|mutant-prestep-git-checkout-head-drift|mutant-gate-post-verify-run-gates-rewrite|mutant-gate-missing-ci-yml-verify|mutant-gate-missing-helper-source|mutant-sentinel-extra-command|mutant-sentinel-v3-proof-preimage|mutant-workflow-dispatch-enabled|mutant-workflow-dispatch-flow-sequence|mutant-prestep-command-git-checkout-head-drift|mutant-sentinel-printf-redirect-side-effect|mutant-prestep-truncate-run-gates|mutant-gate-inline-git-checkout-before-run|mutant-prestep-env-git-checkout-head-drift|mutant-prestep-usrbin-git-checkout-head-drift|mutant-prestep-git-C-checkout-head-drift|mutant-prestep-tee-run-gates|mutant-prestep-colon-redir-run-gates|mutant-sentinel-exec-printf-side-effect|mutant-sentinel-builtin-printf-side-effect|mutant-sentinel-expected-proof-overwrite}" ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-workflow-env-git-dir|mutant-job-env-git-work-tree|mutant-step-env-git-object-directory}" +ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-workflow-dispatch-block-sequence|mutant-prestep-var-run-gates-writer|mutant-prestep-write-trusted-git|mutant-workflow-env-git-namespace|mutant-job-env-git-ceiling-directories}" TRACKED_FILES="$WORK/tracked-files.txt" if ! "$GIT_BIN" ls-files >"$TRACKED_FILES"; then fail "G-18 tracked file scan completed" "git ls-files failed" @@ -518,7 +520,9 @@ for poison_name in \ GIT_TRACE_SETUP \ GIT_TRACE_PERFORMANCE \ GIT_SSH \ - GIT_SSH_COMMAND + GIT_SSH_COMMAND \ + GIT_NAMESPACE \ + GIT_CEILING_DIRECTORIES do if ( export "$poison_name=$WORK/poison" @@ -673,10 +677,13 @@ mutant-gate-post-verify-run-gates-rewrite.yml mutant-sentinel-extra-command.yml mutant-sentinel-v3-proof-preimage.yml mutant-workflow-dispatch-enabled.yml +mutant-workflow-dispatch-block-sequence.yml mutant-workflow-dispatch-flow-sequence.yml mutant-prestep-command-git-checkout-head-drift.yml mutant-sentinel-printf-redirect-side-effect.yml mutant-prestep-truncate-run-gates.yml +mutant-prestep-var-run-gates-writer.yml +mutant-prestep-write-trusted-git.yml mutant-gate-inline-git-checkout-before-run.yml mutant-prestep-env-git-checkout-head-drift.yml mutant-prestep-usrbin-git-checkout-head-drift.yml @@ -789,7 +796,9 @@ mutant-step-quoted-if-run.yml mutant-step-uses-upload-artifact.yml mutant-workflow-env-path-poison.yml mutant-workflow-env-git-dir.yml +mutant-workflow-env-git-namespace.yml mutant-job-env-git-work-tree.yml +mutant-job-env-git-ceiling-directories.yml mutant-workflow-default-shell-alias-or-true.yml mutant-workflow-default-shell-flow-map-or-true.yml mutant-workflow-default-shell-merge-key-or-true.yml From ec8708d68aa018c186af7789201960d94b7d9382 Mon Sep 17 00:00:00 2001 From: rene-founder Date: Tue, 7 Jul 2026 20:34:07 +0100 Subject: [PATCH 9/9] fix(g19): reject GIT_PAGER in proof-critical env scan --- .github/workflows/ci.yml | 14 ++++++++--- .../g19-v2/mutant-job-env-git-pager.yml | 23 +++++++++++++++++++ tests/g19-v2-structural-check.sh | 2 ++ tests/run-gates.sh | 9 +++++--- 4 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/g19-v2/mutant-job-env-git-pager.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e795b81..ee03b33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,6 +122,10 @@ jobs: echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CEILING_DIRECTORIES)" exit 1 fi + if [ -n "${GIT_PAGER:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_PAGER)" + exit 1 + fi git_config_env="$(env | sed -n 's/^\(GIT_CONFIG_[^=]*\)=.*/\1/p' | head -n 1)" if [ -n "$git_config_env" ]; then echo "G-19 FAIL: proof-critical git config environment variable is set ($git_config_env)" @@ -176,12 +180,12 @@ jobs: - name: G-19 CI anti-theater (run-gates execution proof required) if: always() env: - VT_G19_EXPECTED_RUN_GATES_SHA: "af56651d70f46dfcf124bed4e41632565be87eacbd123ba13a7348ad99d15283" - VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "db8e425f0f23914c37bcfb4f99a05eaf7c1e5ff75ff78dd0e9eeb8c651258232" + VT_G19_EXPECTED_RUN_GATES_SHA: "d1ec3239d04d0cd3e716a2a6054befb76723146396f2de25550b194c36ff234b" + VT_G19_EXPECTED_STRUCTURAL_CHECK_SHA: "4863e581cfa202621ca8a1529c0fca44faa348b4df30e1eb31e84addc1432716" VT_G19_EXPECTED_WORKSPACE_HELPER_SHA: "e105de0bca00c509bfff6923515cccb9ab416013a20f17ff90a7159c52890a06" VT_G19_EXPECTED_VERIFY_PY_SHA: "abedd126eac0227dddad9267d8863d1b3b4921ef0bdc69301f59b9c9d708683b" VT_G19_EXPECTED_VERIFY_SH_SHA: "65694b5e0abe95f3322c45ab7c69b597d83121ec14001bdbba8e6e83f2224192" - VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "ba4df165f7fa9a5738f5e9d128e6dd935e1ea2ea2a9f5cb6d3b0872faf5f3c3e" + VT_G19_EXPECTED_FIXTURE_MANIFEST_SHA: "ed002608e565b853c03f868097e684361e43b49db6ae75f3567b8ef75dfea8ed" VT_G19_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} VT_G19_PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before || github.sha }} VT_G19_CHECKOUT_SHA: ${{ github.sha }} @@ -234,6 +238,10 @@ jobs: echo "G-19 FAIL: proof-critical git environment variable is set (GIT_CEILING_DIRECTORIES)" exit 1 fi + if [ -n "${GIT_PAGER:-}" ]; then + echo "G-19 FAIL: proof-critical git environment variable is set (GIT_PAGER)" + exit 1 + fi git_config_env="$(env | sed -n 's/^\(GIT_CONFIG_[^=]*\)=.*/\1/p' | head -n 1)" if [ -n "$git_config_env" ]; then echo "G-19 FAIL: proof-critical git config environment variable is set ($git_config_env)" diff --git a/tests/fixtures/g19-v2/mutant-job-env-git-pager.yml b/tests/fixtures/g19-v2/mutant-job-env-git-pager.yml new file mode 100644 index 0000000..d0d49c1 --- /dev/null +++ b/tests/fixtures/g19-v2/mutant-job-env-git-pager.yml @@ -0,0 +1,23 @@ +name: Verification Tools CI +jobs: + gates: + env: + GIT_PAGER: cat + steps: + - name: Run verification gate suite + id: run_gates + run: | + bash tests/run-gates.sh + - name: G-19 CI anti-theater (run-gates execution proof required) + if: always() + run: | + if [ "${{ steps.run_gates.outcome }}" != "success" ]; then + exit 1 + fi + PROOF="${{ steps.run_gates.outputs.vt_g19_exec_proof }}" + if [ -z "$PROOF" ]; then + exit 1 + fi + if ! printf '%s\n' "$PROOF" | grep -qE '^[0-9a-f]{64}$'; then + exit 1 + fi \ No newline at end of file diff --git a/tests/g19-v2-structural-check.sh b/tests/g19-v2-structural-check.sh index 9831f11..1c489a7 100644 --- a/tests/g19-v2-structural-check.sh +++ b/tests/g19-v2-structural-check.sh @@ -1053,6 +1053,7 @@ if top_jobs_index is not None: allowed_gate_digests = { "1a047f357bfa20490e35bf61f4d32c271b286cd40b3430acc20ebb17222e631f", "8c7e04e0a7acfc3877047e5e4154c3ceda231891e59c484b778051d961f77ff9", + "f7f87489b2d30cd8c0f90312f26c949a1cbe25de94a1a2fd4562d1b7d68fcef1", } if gate_exec != expected_gate_exec and exec_digest(gate_exec) not in allowed_gate_digests: fail("gate execution step must match the exact strict proof command sequence") @@ -1237,6 +1238,7 @@ if top_jobs_index is not None: allowed_sentinel_digests = { "e31864ca935eb2778bcae7f07bfd049524b280a7f6b29a30bf148ba5b94961da", "d511d151880b1e4cb9a7b8cd62c760a2cbc21528855fe28c151c217755aa2c3c", + "93fab5f540b984ae08bc56862bca67e8256bf8fd660549d19dc6c49ffff741f9", } if sentinel_exec != expected_sentinel_exec and exec_digest(sentinel_exec) not in allowed_sentinel_digests: fail("sentinel step must match the exact strict proof command sequence") diff --git a/tests/run-gates.sh b/tests/run-gates.sh index 6d17779..96675c9 100644 --- a/tests/run-gates.sh +++ b/tests/run-gates.sh @@ -40,7 +40,8 @@ reject_git_environment_poisoning() { GIT_SSH \ GIT_SSH_COMMAND \ GIT_NAMESPACE \ - GIT_CEILING_DIRECTORIES + GIT_CEILING_DIRECTORIES \ + GIT_PAGER do if printenv "$name" >/dev/null 2>&1; then echo "SETUP FAIL: proof-critical git environment variable is set ($name)" >&2 @@ -441,7 +442,7 @@ ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-pres ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-inline-anonymous-run-pipe2-chomp-github-env|mutant-inline-run-plain-continuation-github-env-bashenv|mutant-inline-run-plain-continuation-github-output-proof|mutant-inline-run-single-quoted-continuation-github-env-bashenv|mutant-inline-run-single-quoted-continuation-github-output-proof}" ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|baseline-no-working-directory-safe|mutant-prestep-shadow-run-gates-stub|mutant-prestep-shadow-structural-checker-stub|mutant-prestep-shadow-verifier-stub|mutant-step-working-directory-shadow-tree|mutant-job-working-directory-shadow-tree|mutant-prestep-block-ansi-octal-137-env|mutant-prestep-block-ansi-octal-137-output-proof|mutant-prestep-block-ansi-octal-137-path|mutant-prestep-block-ansi-octal-bashenv|mutant-prestep-source-github-env|mutant-prestep-dot-github-env|mutant-inline-run-single-quoted-doubled-quote-github-env|mutant-sentinel-proof-variable-option-plain-nameref-overwrite|mutant-prestep-command-substitution-bash-c-github-env|mutant-prestep-bash-ec-github-env|mutant-prestep-process-substitution-bash-github-env|mutant-prestep-git-checkout-head-drift|mutant-gate-post-verify-run-gates-rewrite|mutant-gate-missing-ci-yml-verify|mutant-gate-missing-helper-source|mutant-sentinel-extra-command|mutant-sentinel-v3-proof-preimage|mutant-workflow-dispatch-enabled|mutant-workflow-dispatch-flow-sequence|mutant-prestep-command-git-checkout-head-drift|mutant-sentinel-printf-redirect-side-effect|mutant-prestep-truncate-run-gates|mutant-gate-inline-git-checkout-before-run|mutant-prestep-env-git-checkout-head-drift|mutant-prestep-usrbin-git-checkout-head-drift|mutant-prestep-git-C-checkout-head-drift|mutant-prestep-tee-run-gates|mutant-prestep-colon-redir-run-gates|mutant-sentinel-exec-printf-side-effect|mutant-sentinel-builtin-printf-side-effect|mutant-sentinel-expected-proof-overwrite}" ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-workflow-env-git-dir|mutant-job-env-git-work-tree|mutant-step-env-git-object-directory}" -ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-workflow-dispatch-block-sequence|mutant-prestep-var-run-gates-writer|mutant-prestep-write-trusted-git|mutant-workflow-env-git-namespace|mutant-job-env-git-ceiling-directories}" +ALLOWED="${ALLOWED/skipped-run_gates-mutant/skipped-run_gates-mutant|mutant-workflow-dispatch-block-sequence|mutant-prestep-var-run-gates-writer|mutant-prestep-write-trusted-git|mutant-workflow-env-git-namespace|mutant-job-env-git-ceiling-directories|mutant-job-env-git-pager}" TRACKED_FILES="$WORK/tracked-files.txt" if ! "$GIT_BIN" ls-files >"$TRACKED_FILES"; then fail "G-18 tracked file scan completed" "git ls-files failed" @@ -522,7 +523,8 @@ for poison_name in \ GIT_SSH \ GIT_SSH_COMMAND \ GIT_NAMESPACE \ - GIT_CEILING_DIRECTORIES + GIT_CEILING_DIRECTORIES \ + GIT_PAGER do if ( export "$poison_name=$WORK/poison" @@ -799,6 +801,7 @@ mutant-workflow-env-git-dir.yml mutant-workflow-env-git-namespace.yml mutant-job-env-git-work-tree.yml mutant-job-env-git-ceiling-directories.yml +mutant-job-env-git-pager.yml mutant-workflow-default-shell-alias-or-true.yml mutant-workflow-default-shell-flow-map-or-true.yml mutant-workflow-default-shell-merge-key-or-true.yml