Skip to content

chore: raise the OpenSSF Scorecard from 6.5 (advisory sweep + signed release SBOMs) #653

chore: raise the OpenSSF Scorecard from 6.5 (advisory sweep + signed release SBOMs)

chore: raise the OpenSSF Scorecard from 6.5 (advisory sweep + signed release SBOMs) #653

Workflow file for this run

name: Test deployment
# Security: the build and the deploy are SEPARATE jobs, and only `deploy`
# holds the Cloudflare credentials.
#
# `build` installs and executes PR-authored code — `pnpm install`, plus every
# build-time hook the repo owns (docs/docusaurus.config.js,
# bulma-ui/.storybook/main.ts, any package.json script). Those files are NOT
# covered by the AI agents' `--disallowedTools` deny lists (which protect
# .github/**, pnpm-workspace.yaml, the jest/commitlint/release configs), so a
# claude/* PR can legitimately change code that runs here before any human
# reviews it. Keeping CLOUDFLARE_API_TOKEN out of that job means build-time
# code has no credential to reach for.
#
# `deploy` runs no repo code at all: it downloads the built site as an
# artifact and hands it to wrangler. Deliberately no checkout. Same split as
# visual-regression.yml and story-screenshots.yml.
on:
pull_request:
branches:
- main
# No workflow-level grants; each job takes exactly what it needs.
permissions: {}
jobs:
build:
name: Build preview site
runs-on: ubuntu-latest
# Read-only, and no secrets: this job executes PR-authored code.
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '24'
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Docusaurus
run: pnpm exec turbo run build --filter=@allxsmith/bestax-docs
- name: Build Storybook
run: pnpm --filter @allxsmith/bestax-bulma run build-storybook
- name: Copy Storybook to Docusaurus build
run: |
mkdir -p docs/build/storybook
cp -r bulma-ui/storybook-static/* docs/build/storybook/
# The deploy job never checks out the repo — this artifact is the only
# thing that crosses the boundary. Fail loudly if the build produced
# nothing rather than deploying an empty site over the preview.
- name: Upload preview site
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: preview-site
path: docs/build
retention-days: 1
if-no-files-found: error
# Job id and display name are unchanged from the single-job version so any
# required status check referencing "Test and Preview Deployment" still
# resolves.
test-deploy:
name: Test and Preview Deployment
runs-on: ubuntu-latest
needs: build
# Dependabot-triggered runs are scoped to the *Dependabot* secrets store,
# not the Actions one, so `secrets.CLOUDFLARE_API_TOKEN` interpolates to an
# empty string and wrangler aborts with "it's necessary to set a
# CLOUDFLARE_API_TOKEN environment variable". That made this check red on
# every dependency PR — noise that trains reviewers to ignore a red check.
#
# `github.actor` is the right condition because it is the same thing GitHub
# keys the secret scoping on: a human pushing to a dependabot/* branch
# becomes the actor and does get the Actions secrets, so the preview still
# deploys in exactly the cases where it can.
#
# Fork PRs lose the secrets the same way, and deliberately — this workflow
# is plain `pull_request`, so a fork head never sees them (rule 7). The
# actor there is the contributor rather than dependabot, so the actor test
# alone does not cover them and the head-repo guard is what does. Without
# it a fork PR reproduces the failure this job was guarded against: the
# deploy on an empty token, and then Comment Preview URL, whose write
# permission GitHub downgrades to read on a fork run whatever the
# `permissions:` block below says. Same bare idiom as claude-review.yml
# and story-screenshots.yml; ai-scan.yml and ai-triage.yml wrap it in a
# `pull_request == null` branch only because they also run on issues,
# which this workflow does not. Both clauses narrow what runs — the
# trigger above is unchanged.
#
# Only this job is guarded. `build` still runs on dependency PRs, so a bump
# that breaks the Docusaurus or Storybook build is still caught.
#
# This reports as *skipped*, not success. Fine while the check is not
# required (the main ruleset requires Build and Test, the React 18/19
# matrix, and Dependency Review). If it is ever promoted to required, move
# this condition down to the steps so the job itself still concludes green.
#
# Adding the Cloudflare credentials to the Dependabot secrets store would
# also turn the check green, and is deliberately not done: it would hand a
# live deploy token to auto-generated PRs that pull in unreviewed
# transitive dependencies — the exact exposure the build/deploy split above
# exists to prevent.
if: |
github.actor != 'dependabot[bot]' &&
github.event.pull_request.head.repo.full_name == github.repository
permissions:
pull-requests: write # comment the preview URL
steps:
- name: Download preview site
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: preview-site
path: docs/build
# github.head_ref is attacker-chosen text (the PR branch name) — never
# interpolate it into a command. Reduce it to [A-Za-z0-9-] first; the
# sanitized output is safe to interpolate by construction and matches
# Cloudflare's branch-alias charset.
- name: Sanitize preview branch name
id: branch
env:
HEAD_REF: ${{ github.head_ref }}
run: |
SAFE=$(printf '%s' "$HEAD_REF" | tr -cs 'a-zA-Z0-9-' '-' | cut -c1-63)
echo "safe=$SAFE" >> "$GITHUB_OUTPUT"
- name: Deploy Preview to Cloudflare Pages
uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4
id: deploy
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy docs/build --project-name=bestax --branch=${{ steps.branch.outputs.safe }} --commit-dirty=true
- name: Comment Preview URL
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '## Preview Deployment\n\nPreview URL: ${{ steps.deploy.outputs.deployment-url }}'
})