From f9bda773b8c46e133809db3391e11ba28fa59a1b Mon Sep 17 00:00:00 2001 From: margaretjgu Date: Tue, 14 Jul 2026 17:45:23 -0400 Subject: [PATCH 1/3] ci: auto-fix schema on /fix-schema comment --- .github/workflows/ci.yml | 9 +++++ .github/workflows/schema-auto-fix.yml | 58 +++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 .github/workflows/schema-auto-fix.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc131348..94b660d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,6 +128,7 @@ jobs: name: Check CLI schema is up to date permissions: contents: read + pull-requests: write runs-on: ubuntu-latest env: NODE_OPTIONS: --max-old-space-size=6144 @@ -151,6 +152,14 @@ jobs: echo "docs/cli/schema.json is out of date. Run 'npm run build:schema' and commit the result." exit 1 fi + - name: Comment on PR + if: failure() && github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + gh pr comment "$PR_NUMBER" --body " + \`docs/cli/schema.json\` is out of date. Comment \`/fix-schema\` on this PR and the bot will regenerate and commit it automatically." ci: name: CI Result diff --git a/.github/workflows/schema-auto-fix.yml b/.github/workflows/schema-auto-fix.yml new file mode 100644 index 00000000..8c2ec642 --- /dev/null +++ b/.github/workflows/schema-auto-fix.yml @@ -0,0 +1,58 @@ +name: Auto-fix schema + +on: + issue_comment: + types: [created] + +jobs: + fix: + name: Regenerate and commit schema + if: github.event.issue.pull_request != null && contains(github.event.comment.body, '/fix-schema') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + env: + NODE_OPTIONS: --max-old-space-size=6144 + steps: + - name: Get PR branch + id: pr + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.issue.number }} + run: | + BRANCH=$(gh pr view "$PR_NUMBER" --json headRefName -q .headRefName) + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + ref: ${{ steps.pr.outputs.branch }} + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 24.x + + - name: Install + run: npm ci + + - name: Build + run: npm run build + + - name: Regenerate CLI schema + run: npm run build:schema + + - name: Commit and push + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.issue.number }} + run: | + if git diff --quiet docs/cli/schema.json; then + gh pr comment "$PR_NUMBER" --body "Schema is already up to date - nothing to commit." + else + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add docs/cli/schema.json + git commit -m "chore: regenerate CLI schema" + git push + gh pr comment "$PR_NUMBER" --body "Schema regenerated and committed." + fi From 040c6a878019f093a5f12c4bb3dbbe4ea1b94828 Mon Sep 17 00:00:00 2001 From: margaretjgu Date: Wed, 22 Jul 2026 16:39:37 -0400 Subject: [PATCH 2/3] ci: restrict /fix-schema to maintainers and same-repo branches --- .github/workflows/schema-auto-fix.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/schema-auto-fix.yml b/.github/workflows/schema-auto-fix.yml index 8c2ec642..a271eb2f 100644 --- a/.github/workflows/schema-auto-fix.yml +++ b/.github/workflows/schema-auto-fix.yml @@ -7,7 +7,12 @@ on: jobs: fix: name: Regenerate and commit schema - if: github.event.issue.pull_request != null && contains(github.event.comment.body, '/fix-schema') + # Only run for maintainer comments: this job checks out and builds the PR + # branch with a write token, so it must never be triggerable by outsiders. + if: | + github.event.issue.pull_request != null && + contains(github.event.comment.body, '/fix-schema') && + contains(fromJSON('["MEMBER", "OWNER", "COLLABORATOR"]'), github.event.comment.author_association) runs-on: ubuntu-latest permissions: contents: write @@ -21,7 +26,12 @@ jobs: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.issue.number }} run: | - BRANCH=$(gh pr view "$PR_NUMBER" --json headRefName -q .headRefName) + BRANCH=$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json headRefName -q .headRefName) + FORK=$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json isCrossRepository -q .isCrossRepository) + if [ "$FORK" = "true" ]; then + echo "Refusing to run against a fork branch." >&2 + exit 1 + fi echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 From 7c570989cde6ced6e7a6133fde947bfeba4695c9 Mon Sep 17 00:00:00 2001 From: margaretjgu Date: Wed, 22 Jul 2026 16:50:09 -0400 Subject: [PATCH 3/3] ci: pin checkout to validated SHA to prevent TOCTOU --- .github/workflows/schema-auto-fix.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/schema-auto-fix.yml b/.github/workflows/schema-auto-fix.yml index a271eb2f..d7b35319 100644 --- a/.github/workflows/schema-auto-fix.yml +++ b/.github/workflows/schema-auto-fix.yml @@ -26,17 +26,19 @@ jobs: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.issue.number }} run: | - BRANCH=$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json headRefName -q .headRefName) - FORK=$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json isCrossRepository -q .isCrossRepository) - if [ "$FORK" = "true" ]; then + PR_JSON=$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json headRefName,headRefOid,isCrossRepository) + if [ "$(echo "$PR_JSON" | jq -r .isCrossRepository)" = "true" ]; then echo "Refusing to run against a fork branch." >&2 exit 1 fi - echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + echo "branch=$(echo "$PR_JSON" | jq -r .headRefName)" >> "$GITHUB_OUTPUT" + echo "sha=$(echo "$PR_JSON" | jq -r .headRefOid)" >> "$GITHUB_OUTPUT" + # Check out the exact SHA validated above (not the branch name) so the + # ref cannot be swapped between validation and execution (TOCTOU). - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: - ref: ${{ steps.pr.outputs.branch }} + ref: ${{ steps.pr.outputs.sha }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: @@ -55,6 +57,8 @@ jobs: env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.issue.number }} + BRANCH: ${{ steps.pr.outputs.branch }} + SHA: ${{ steps.pr.outputs.sha }} run: | if git diff --quiet docs/cli/schema.json; then gh pr comment "$PR_NUMBER" --body "Schema is already up to date - nothing to commit." @@ -63,6 +67,7 @@ jobs: git config user.email "github-actions[bot]@users.noreply.github.com" git add docs/cli/schema.json git commit -m "chore: regenerate CLI schema" - git push + # Fail instead of clobbering if the branch moved since validation. + git push origin "HEAD:refs/heads/$BRANCH" --force-with-lease="refs/heads/$BRANCH:$SHA" gh pr comment "$PR_NUMBER" --body "Schema regenerated and committed." fi