From 6114a528f07758842fb8d4748fd17ca0e46bca54 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 11:15:26 +0000 Subject: [PATCH 1/3] ci: harden GitHub Actions security posture per zizmor Pin all third-party actions to commit SHAs, add a zizmor config that scopes the hash-pin requirement to everything except the org's chinmina/.github reusable workflows (trusted via the protected verified-actions branch). Also: - drop id-token/deployments write permissions at the workflow level; neither is used by any step (id-token was a leftover from the removed GitHub Pages/OIDC deploy, deployments write is unused by the current script invocation) - scope pull-requests: write to the deploy-cloudflare job only, since it's the sole consumer (PR comment posting) - set persist-credentials: false on checkout steps to stop the checked-out credential from leaking into build output/artifacts --- .github/workflows/deploy.yaml | 22 +++++++++++++--------- .github/zizmor.yml | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 .github/zizmor.yml diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 277e7fd..eceda68 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -8,18 +8,17 @@ on: permissions: contents: read - id-token: write - deployments: write - pull-requests: write jobs: build: runs-on: ubuntu-latest steps: - name: Checkout your repository using git - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false - name: Install tools with mise - uses: jdx/mise-action@v4 + uses: jdx/mise-action@3c2e0cf82a5b2e5249f0d3635a4d83d0ae861518 # v4 - name: Enable corepack run: corepack enable - name: Install dependencies @@ -27,7 +26,7 @@ jobs: - name: Build site run: pnpm run build - name: Upload dist artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: dist path: dist/ @@ -36,15 +35,20 @@ jobs: needs: build if: github.event_name != 'pull_request' || (github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write environment: name: cloudflare url: ${{ steps.cf-url.outputs.value }} steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false - name: Install tools with mise - uses: jdx/mise-action@v4 + uses: jdx/mise-action@3c2e0cf82a5b2e5249f0d3635a4d83d0ae861518 # v4 with: install_args: node @@ -55,7 +59,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Download dist artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: dist path: dist/ diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 0000000..4738315 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,14 @@ +# zizmor configuration +# +# https://docs.zizmor.sh/configuration/ + +rules: + unpinned-uses: + config: + policies: + # The org's shared reusable workflows are trusted via the + # `verified-actions` branch, which is protected and reviewed. + # Pin to that branch rather than a hash so updates land + # automatically when the branch is fast-forwarded. + "chinmina/.github/*": ref-pin + "*": hash-pin From bd2a163a34c30715cc4cf95bc3d7f289b36b8694 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 11:25:09 +0000 Subject: [PATCH 2/3] ci: wire up the deployment-record path in cloudflare-deploy.sh The deploy workflow only ever called the script's `comment` subcommand; `deployment` (which creates a proper GitHub Deployment with Cloudflare project/dashboard metadata) was dead code. Call it after each successful deploy and restore the deployments: write permission it needs, scoped to the deploy-cloudflare job. Trim the now-redundant step summary from the URL-extraction step since the deployment step writes a fuller one. --- .github/workflows/deploy.yaml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index eceda68..b9e678e 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -37,6 +37,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + deployments: write pull-requests: write environment: name: cloudflare @@ -81,8 +82,14 @@ jobs: | jq -r 'select(.type == "pages-deploy-detailed") | .url // empty' \ | head -1) echo "value=${url}" >> "$GITHUB_OUTPUT" - echo "### Cloudflare Pages" >> "$GITHUB_STEP_SUMMARY" - echo "**URL:** ${url}" >> "$GITHUB_STEP_SUMMARY" + + - name: Create deployment record + if: always() && steps.deploy.outcome == 'success' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WRANGLER_OUTPUT_FILE_DIRECTORY: .wrangler-output + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: .github/scripts/cloudflare-deploy.sh deployment - name: Post PR comment if: github.event_name == 'pull_request' From 751eb01ceeea100375a515bd0c3b72c3e8ff519e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 14:43:11 +0000 Subject: [PATCH 3/3] Revert "ci: wire up the deployment-record path in cloudflare-deploy.sh" This reverts commit bd2a163a34c30715cc4cf95bc3d7f289b36b8694. The cloudflare-scoped GitHub Environment gates access to the Cloudflare API token/account ID secrets, and the job's `environment:` key already creates a deployment record tied to that same protected environment. Restoring deployments: write just to create a second, differently-labelled deployment record isn't worth the extra permission surface on a job that already handles deploy secrets. --- .github/workflows/deploy.yaml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index b9e678e..eceda68 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -37,7 +37,6 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - deployments: write pull-requests: write environment: name: cloudflare @@ -82,14 +81,8 @@ jobs: | jq -r 'select(.type == "pages-deploy-detailed") | .url // empty' \ | head -1) echo "value=${url}" >> "$GITHUB_OUTPUT" - - - name: Create deployment record - if: always() && steps.deploy.outcome == 'success' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WRANGLER_OUTPUT_FILE_DIRECTORY: .wrangler-output - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - run: .github/scripts/cloudflare-deploy.sh deployment + echo "### Cloudflare Pages" >> "$GITHUB_STEP_SUMMARY" + echo "**URL:** ${url}" >> "$GITHUB_STEP_SUMMARY" - name: Post PR comment if: github.event_name == 'pull_request'