From 2f925e89d2a09193ef60fac617fab5d7f414cd9f Mon Sep 17 00:00:00 2001 From: Don Petry <36422719+don-petry@users.noreply.github.com> Date: Mon, 12 Oct 2026 21:09:04 -0500 Subject: [PATCH 01/11] fix(ci): add pr-review.yml to .prettierignore and use GH_PAT_WORKFLOWS for autofix --- .github/workflows/ci.yml | 2 +- .prettierignore | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e9732fb..d0378bf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.event.pull_request.head.ref }} - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GH_PAT_WORKFLOWS || secrets.GITHUB_TOKEN }} - name: Setup Node.js uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 diff --git a/.prettierignore b/.prettierignore index abfce3c3..03c026b7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -33,3 +33,9 @@ # central byte-identity stub-drift guard. Prettier would collapse that gap and # reflow comments, re-introducing drift, so it must not reformat this file. .github/workflows/persona-mention.yml +# pr-review is a standards-owned caller stub copied VERBATIM from +# petry-projects/.github/standards/workflows/pr-review.yml. Its exact bytes +# (including the two-space gap before the `# NOSONAR` marker) are enforced by the +# central byte-identity stub-drift guard. Prettier would collapse that gap and +# reflow comments, re-introducing drift, so it must not reformat this file. +.github/workflows/pr-review.yml From 960d9079d7970b8753a62519acdd3ff220d99b84 Mon Sep 17 00:00:00 2001 From: Don Petry <36422719+don-petry@users.noreply.github.com> Date: Tue, 13 Oct 2026 07:06:38 -0500 Subject: [PATCH 02/11] fix(ci): isolate GH_PAT_WORKFLOWS to push step to prevent credential exfiltration during npm ci --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0378bf4..a739e217 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.event.pull_request.head.ref }} - token: ${{ secrets.GH_PAT_WORKFLOWS || secrets.GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Node.js uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 @@ -88,6 +88,8 @@ jobs: npm run lint -- --fix - name: Commit and push auto-fixes + env: + GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS || secrets.GITHUB_TOKEN }} run: | if git diff --quiet; then echo "No formatting or lint fixes needed." @@ -96,7 +98,7 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" git add -A git commit -m "chore(ci): apply prettier/eslint auto-fixes" - git push + git push "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" HEAD:${{ github.event.pull_request.head.ref }} fi - name: Verify no remaining issues after auto-fix From 2d50290a755d3f5b766d610398575c46d06d1c59 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:02:51 +0000 Subject: [PATCH 03/11] fix(ci): prevent branch name injection in git push with token Pass the branch name through a quoted environment variable to prevent shell metacharacters in branch names from being interpreted when GH_TOKEN is available, closing a potential command injection vector. Co-Authored-By: Claude Haiku 4.5 --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a739e217..d9febc62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,6 +90,7 @@ jobs: - name: Commit and push auto-fixes env: GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS || secrets.GITHUB_TOKEN }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} run: | if git diff --quiet; then echo "No formatting or lint fixes needed." @@ -98,7 +99,7 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" git add -A git commit -m "chore(ci): apply prettier/eslint auto-fixes" - git push "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" HEAD:${{ github.event.pull_request.head.ref }} + git push "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" HEAD:"${HEAD_REF}" fi - name: Verify no remaining issues after auto-fix From a2c98359f8f94510c8ec2d3cdabe8c158431fb4b Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:06:58 +0000 Subject: [PATCH 04/11] fix(ci): do not persist checkout credentials in autofix job Co-Authored-By: Claude Sonnet 5.5 --- .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 d9febc62..2da51a3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.event.pull_request.head.ref }} - token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false - name: Setup Node.js uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6 From 2478e66215975ee54b6c49379965bed30f25f933 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:19:28 +0000 Subject: [PATCH 05/11] fix(reviews): push autofix from a clean checkout job so PR hooks never see the token Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 49 +++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2da51a3f..6f79cda5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,9 @@ jobs: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest permissions: - contents: write + contents: read + outputs: + patch: ${{ steps.patch.outputs.patch }} steps: - name: Checkout code @@ -87,23 +89,44 @@ jobs: npm run format npm run lint -- --fix + # No push token is available in this job: PR-controlled tooling (npm + # scripts, git hooks) runs here, so the fixes are exported as a patch and + # applied + pushed by the separate 'autofix-push' job on a clean checkout. + - name: Export auto-fixes as patch + id: patch + run: | + git add -A + echo "patch=$(git diff --cached --binary | base64 -w0)" >> "$GITHUB_OUTPUT" + + - name: Verify no remaining issues after auto-fix + run: npm run check + + autofix-push: + needs: autofix + if: needs.autofix.outputs.patch != '' + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout code (clean, no PR-installed tooling) + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.ref }} + persist-credentials: false + - name: Commit and push auto-fixes env: GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS || secrets.GITHUB_TOKEN }} HEAD_REF: ${{ github.event.pull_request.head.ref }} + PATCH_B64: ${{ needs.autofix.outputs.patch }} run: | - if git diff --quiet; then - echo "No formatting or lint fixes needed." - else - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add -A - git commit -m "chore(ci): apply prettier/eslint auto-fixes" - git push "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" HEAD:"${HEAD_REF}" - fi - - - name: Verify no remaining issues after auto-fix - run: npm run check + printf '%s' "${PATCH_B64}" | base64 -d > "${RUNNER_TEMP}/autofix.patch" + git apply --index "${RUNNER_TEMP}/autofix.patch" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git commit -m "chore(ci): apply prettier/eslint auto-fixes" + git push "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" HEAD:"${HEAD_REF}" secret-scan: name: Secret scan (gitleaks) From 1976d1070065bc4a296032b736ecc46c66828b83 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:17:55 +0000 Subject: [PATCH 06/11] fix(reviews): pass autofix patch via artifact instead of job output --- .github/workflows/ci.yml | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f79cda5..53ed029e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: permissions: contents: read outputs: - patch: ${{ steps.patch.outputs.patch }} + has_patch: ${{ steps.patch.outputs.has_patch }} steps: - name: Checkout code @@ -96,14 +96,28 @@ jobs: id: patch run: | git add -A - echo "patch=$(git diff --cached --binary | base64 -w0)" >> "$GITHUB_OUTPUT" + git diff --cached --binary > "${RUNNER_TEMP}/autofix.patch" + if [ -s "${RUNNER_TEMP}/autofix.patch" ]; then + echo "has_patch=true" >> "$GITHUB_OUTPUT" + else + echo "has_patch=false" >> "$GITHUB_OUTPUT" + fi + + # Passed as an artifact: job outputs are capped at ~1 MB. + - name: Upload auto-fix patch + if: steps.patch.outputs.has_patch == 'true' + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 + with: + name: autofix-patch + path: ${{ runner.temp }}/autofix.patch + if-no-files-found: error - name: Verify no remaining issues after auto-fix run: npm run check autofix-push: needs: autofix - if: needs.autofix.outputs.patch != '' + if: needs.autofix.outputs.has_patch == 'true' runs-on: ubuntu-latest permissions: contents: write @@ -115,14 +129,18 @@ jobs: ref: ${{ github.event.pull_request.head.ref }} persist-credentials: false + - name: Download auto-fix patch + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: autofix-patch + path: ${{ runner.temp }}/autofix-patch + - name: Commit and push auto-fixes env: GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS || secrets.GITHUB_TOKEN }} HEAD_REF: ${{ github.event.pull_request.head.ref }} - PATCH_B64: ${{ needs.autofix.outputs.patch }} run: | - printf '%s' "${PATCH_B64}" | base64 -d > "${RUNNER_TEMP}/autofix.patch" - git apply --index "${RUNNER_TEMP}/autofix.patch" + git apply --index "${RUNNER_TEMP}/autofix-patch/autofix.patch" git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git commit -m "chore(ci): apply prettier/eslint auto-fixes" From 770c3694e6d59941a93848b3d0363cff5760b016 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:24:01 +0000 Subject: [PATCH 07/11] fix(reviews): require GH_PAT_WORKFLOWS for autofix push Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53ed029e..48c327ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -137,9 +137,13 @@ jobs: - name: Commit and push auto-fixes env: - GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS || secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS }} HEAD_REF: ${{ github.event.pull_request.head.ref }} run: | + if [ -z "${GH_TOKEN}" ]; then + echo "::error::GH_PAT_WORKFLOWS is required for autofix pushes." + exit 1 + fi git apply --index "${RUNNER_TEMP}/autofix-patch/autofix.patch" git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" From 9de7d4827fc8e31943088e3d0cadbd26454643f4 Mon Sep 17 00:00:00 2001 From: Rachel Petry <287457313+rachel-petry@users.noreply.github.com> Date: Fri, 23 Oct 2026 06:26:54 -0500 Subject: [PATCH 08/11] fix(ci): restrict autofix patch to non-workflow tracked files and prefer GH_PAT_DON_PETRY --- .github/workflows/ci.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48c327ad..43c2b3e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,7 +95,7 @@ jobs: - name: Export auto-fixes as patch id: patch run: | - git add -A + git add -u -- . ':(exclude).github/workflows' git diff --cached --binary > "${RUNNER_TEMP}/autofix.patch" if [ -s "${RUNNER_TEMP}/autofix.patch" ]; then echo "has_patch=true" >> "$GITHUB_OUTPUT" @@ -137,11 +137,15 @@ jobs: - name: Commit and push auto-fixes env: - GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS }} + GH_TOKEN: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }} HEAD_REF: ${{ github.event.pull_request.head.ref }} run: | if [ -z "${GH_TOKEN}" ]; then - echo "::error::GH_PAT_WORKFLOWS is required for autofix pushes." + echo "::error::GH_PAT_DON_PETRY (or legacy GH_PAT_WORKFLOWS) is required for autofix pushes." + exit 1 + fi + if git apply --numstat "${RUNNER_TEMP}/autofix-patch/autofix.patch" | awk '{print $3}' | grep -q '^\.github/workflows/'; then + echo "::error::autofix patch touches workflow files; refusing to push." exit 1 fi git apply --index "${RUNNER_TEMP}/autofix-patch/autofix.patch" From a87b9f5ce69b7959dbfbc28a8e6ebae25580f6fa Mon Sep 17 00:00:00 2001 From: Rachel Petry <287457313+rachel-petry@users.noreply.github.com> Date: Fri, 23 Oct 2026 08:30:55 -0500 Subject: [PATCH 09/11] fix(ci): harden autofix workflow-path guard with NUL-delimited checks --- .github/workflows/ci.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43c2b3e4..f41e809e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,11 +144,20 @@ jobs: echo "::error::GH_PAT_DON_PETRY (or legacy GH_PAT_WORKFLOWS) is required for autofix pushes." exit 1 fi - if git apply --numstat "${RUNNER_TEMP}/autofix-patch/autofix.patch" | awk '{print $3}' | grep -q '^\.github/workflows/'; then + numstat_file="${RUNNER_TEMP}/autofix-numstat.bin" + if ! git apply --numstat -z "${RUNNER_TEMP}/autofix-patch/autofix.patch" > "${numstat_file}"; then + echo "::error::unable to inspect autofix patch; refusing to push." + exit 1 + fi + if tr '\0' '\n' < "${numstat_file}" | grep -Eq '^(([0-9]+|-)[[:space:]]+([0-9]+|-)[[:space:]]+)?\.github/workflows/'; then echo "::error::autofix patch touches workflow files; refusing to push." exit 1 fi git apply --index "${RUNNER_TEMP}/autofix-patch/autofix.patch" + if git diff --cached --name-only -z | tr '\0' '\n' | grep -Eq '^\.github/workflows/'; then + echo "::error::autofix patch touches workflow files; refusing to push." + exit 1 + fi git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git commit -m "chore(ci): apply prettier/eslint auto-fixes" From 2757d3b3577ae836e65a3576948d2d6e7e2e37a8 Mon Sep 17 00:00:00 2001 From: Rachel Petry <287457313+rachel-petry@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:21:54 -0500 Subject: [PATCH 10/11] fix(ci): verify staged autofix tree before upload and guard against stale branch head --- .github/workflows/ci.yml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f41e809e..3dab5ed4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,7 @@ jobs: contents: read outputs: has_patch: ${{ steps.patch.outputs.has_patch }} + head_sha: ${{ steps.patch.outputs.head_sha }} steps: - name: Checkout code @@ -97,12 +98,17 @@ jobs: run: | git add -u -- . ':(exclude).github/workflows' git diff --cached --binary > "${RUNNER_TEMP}/autofix.patch" + git checkout -- . + echo "head_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" if [ -s "${RUNNER_TEMP}/autofix.patch" ]; then echo "has_patch=true" >> "$GITHUB_OUTPUT" else echo "has_patch=false" >> "$GITHUB_OUTPUT" fi + - name: Verify no remaining issues after auto-fix + run: npm run check + # Passed as an artifact: job outputs are capped at ~1 MB. - name: Upload auto-fix patch if: steps.patch.outputs.has_patch == 'true' @@ -112,9 +118,6 @@ jobs: path: ${{ runner.temp }}/autofix.patch if-no-files-found: error - - name: Verify no remaining issues after auto-fix - run: npm run check - autofix-push: needs: autofix if: needs.autofix.outputs.has_patch == 'true' @@ -139,29 +142,34 @@ jobs: env: GH_TOKEN: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }} HEAD_REF: ${{ github.event.pull_request.head.ref }} + EXPECTED_SHA: ${{ needs.autofix.outputs.head_sha }} run: | if [ -z "${GH_TOKEN}" ]; then echo "::error::GH_PAT_DON_PETRY (or legacy GH_PAT_WORKFLOWS) is required for autofix pushes." exit 1 fi + if [ "$(git rev-parse HEAD)" != "${EXPECTED_SHA}" ]; then + echo "::notice::PR head moved from ${EXPECTED_SHA} to $(git rev-parse HEAD); skipping stale autofix push." + exit 0 + fi numstat_file="${RUNNER_TEMP}/autofix-numstat.bin" if ! git apply --numstat -z "${RUNNER_TEMP}/autofix-patch/autofix.patch" > "${numstat_file}"; then echo "::error::unable to inspect autofix patch; refusing to push." exit 1 fi - if tr '\0' '\n' < "${numstat_file}" | grep -Eq '^(([0-9]+|-)[[:space:]]+([0-9]+|-)[[:space:]]+)?\.github/workflows/'; then + if tr '\0' '\n' < "${numstat_file}" | grep -Eiq '^(([0-9]+|-)[[:space:]]+([0-9]+|-)[[:space:]]+)?(\./)?\.github/workflows/'; then echo "::error::autofix patch touches workflow files; refusing to push." exit 1 fi git apply --index "${RUNNER_TEMP}/autofix-patch/autofix.patch" - if git diff --cached --name-only -z | tr '\0' '\n' | grep -Eq '^\.github/workflows/'; then + if git diff --cached --name-only -z | tr '\0' '\n' | grep -Eiq '^(\./)?\.github/workflows/'; then echo "::error::autofix patch touches workflow files; refusing to push." exit 1 fi git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git commit -m "chore(ci): apply prettier/eslint auto-fixes" - git push "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" HEAD:"${HEAD_REF}" + git push -- "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" "HEAD:refs/heads/${HEAD_REF}" secret-scan: name: Secret scan (gitleaks) From 51780d2a190a4aa7cbc736f3d9a2a2241e1106a0 Mon Sep 17 00:00:00 2001 From: Rachel Petry <287457313+rachel-petry@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:34:25 -0500 Subject: [PATCH 11/11] fix(ci): reduce autofix-push GITHUB_TOKEN permission to contents: read --- .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 3dab5ed4..aecc87d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -123,7 +123,7 @@ jobs: if: needs.autofix.outputs.has_patch == 'true' runs-on: ubuntu-latest permissions: - contents: write + contents: read steps: - name: Checkout code (clean, no PR-installed tooling)