From d94dbadc2c2e17684306996ec4fa5a029e85ff1e Mon Sep 17 00:00:00 2001 From: Rob Nester Date: Wed, 7 Oct 2026 12:34:30 -0400 Subject: [PATCH 1/3] Harden Cloudflare preview deployments Split artifact preparation from Cloudflare deployment and pass the pull-request number through a job output. Restrict deploy permissions and skip runs whose artifacts come from fork repositories, keeping untrusted content away from the deployment token. Co-Authored-By: Codex Ref: EC-2063 --- .github/workflows/preview.yaml | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml index ca14cacd..db48dfb8 100644 --- a/.github/workflows/preview.yaml +++ b/.github/workflows/preview.yaml @@ -28,11 +28,13 @@ defaults: shell: bash -o errexit -o pipefail -o nounset -o errtrace {0} jobs: - preview: + prepare: permissions: - pull-requests: write + actions: read runs-on: ubuntu-latest if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + outputs: + pr_number: ${{ steps.data.outputs.PR_NUMBER }} steps: - name: Download pull request artifact uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -58,14 +60,32 @@ jobs: run: | mkdir public unzip -q 'artifacts/data-[0-1].zip' -d public - rm -rf artifacts - name: Setup pull request data id: data run: | PR_NUMBER=$(head -1 public/pull_request/number) PR_NUMBER=${PR_NUMBER//[^0-9]/} echo "PR_NUMBER=${PR_NUMBER}" >> $GITHUB_OUTPUT - echo "PR_URL=https://github.com/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}" >> $GITHUB_OUTPUT + - name: Upload prepared website + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: website-preview + path: public + + deploy: + permissions: + actions: read + contents: read + pull-requests: write + runs-on: ubuntu-latest + needs: prepare + if: github.event.workflow_run.head_repository.full_name == github.repository + steps: + - name: Download prepared website + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: website-preview + path: public - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -77,7 +97,7 @@ jobs: CLOUDFLARE_PROJECT_NAME: ${{ vars.CLOUDFLARE_PROJECT_NAME }} run: | npm --prefix code ci - npm --prefix code exec -- wrangler pages deploy public --project-name "$CLOUDFLARE_PROJECT_NAME" --branch pr-${{ steps.data.outputs.PR_NUMBER }} | tee out.txt + npm --prefix code exec -- wrangler pages deploy public --project-name "$CLOUDFLARE_PROJECT_NAME" --branch pr-${{ needs.prepare.outputs.pr_number }} | tee out.txt grep 'Deployment complete! Take a peek over at ' out.txt | sed -e 's/.*over at /PREVIEW_URL=/' >> $GITHUB_OUTPUT - name: Add comment uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -85,7 +105,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | github.rest.issues.createComment({ - issue_number: Number(${{ steps.data.outputs.PR_NUMBER }}), + issue_number: Number(${{ needs.prepare.outputs.pr_number }}), owner: context.repo.owner, repo: context.repo.repo, body: `🚀 Preview is available at ${{ steps.preview.outputs.PREVIEW_URL }}` From f4a98b3e06301d677d850cd5a1e26baddf395c13 Mon Sep 17 00:00:00 2001 From: Rob Nester Date: Wed, 7 Oct 2026 12:53:27 -0400 Subject: [PATCH 2/3] Skip fork artifacts during preparation Apply the repository-origin check before downloading and unpacking preview artifacts, so fork-originated workflow runs are skipped before any artifact processing. Co-Authored-By: Codex Ref: EC-2063 --- .github/workflows/preview.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml index db48dfb8..513b362d 100644 --- a/.github/workflows/preview.yaml +++ b/.github/workflows/preview.yaml @@ -32,7 +32,7 @@ jobs: permissions: actions: read runs-on: ubuntu-latest - if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository outputs: pr_number: ${{ steps.data.outputs.PR_NUMBER }} steps: From 5255b969569b16e368e08ecba9ebb0f481ed6448 Mon Sep 17 00:00:00 2001 From: Rob Nester Date: Wed, 7 Oct 2026 13:23:14 -0400 Subject: [PATCH 3/3] Bind previews to workflow metadata Use workflow-run metadata for the pull-request number and extract only the website artifact to avoid duplicate paths. Co-Authored-By: Codex Ref: EC-2063 --- .github/workflows/preview.yaml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml index 513b362d..90fc008a 100644 --- a/.github/workflows/preview.yaml +++ b/.github/workflows/preview.yaml @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository outputs: - pr_number: ${{ steps.data.outputs.PR_NUMBER }} + pr_number: ${{ github.event.workflow_run.pull_requests[0].number }} steps: - name: Download pull request artifact uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -46,7 +46,9 @@ jobs: run_id: ${{ github.event.workflow_run.id }}, }); const downloads = await Promise.all( - artifacts.data.artifacts.map(a => github.rest.actions.downloadArtifact({ + artifacts.data.artifacts + .filter(a => a.name === 'website') + .map(a => github.rest.actions.downloadArtifact({ owner: context.repo.owner, repo: context.repo.repo, artifact_id: a.id, @@ -59,13 +61,7 @@ jobs: - name: Unzip website artifact run: | mkdir public - unzip -q 'artifacts/data-[0-1].zip' -d public - - name: Setup pull request data - id: data - run: | - PR_NUMBER=$(head -1 public/pull_request/number) - PR_NUMBER=${PR_NUMBER//[^0-9]/} - echo "PR_NUMBER=${PR_NUMBER}" >> $GITHUB_OUTPUT + unzip -q artifacts/data-0.zip -d public - name: Upload prepared website uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: