name: Compliance diff # Stage 1 of the fork-safe compliance review (see swedish-compliance-review.yml). # # This runs on the untrusted PR head, but is SAFE because it has NO secrets and # only a read-only token: it computes the diff and uploads it as an artifact. # It never runs project code (no `npm install`, no `node`): only git plumbing, # which does not execute repository hooks. The privileged half (model call + # comment) lives in stage 2, which never checks out fork code. on: pull_request: types: [opened, synchronize, reopened] permissions: contents: read jobs: prepare: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - name: Compute diff vs base # Pass GitHub context via env, never interpolate ${{ }} into the shell # body: expression substitution happens before bash parses the script, # so a value with shell metacharacters would be a code-execution sink. env: BASE_REF: ${{ github.base_ref }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | set -euo pipefail # Full fetch (not --depth=1): the PR branch may be behind base, and a # shallow base can leave merge-base with no reachable common ancestor. # checkout above uses fetch-depth: 0, so HEAD already has full history. git fetch origin "$BASE_REF" MERGE_BASE=$(git merge-base "origin/$BASE_REF" HEAD) git diff "$MERGE_BASE" HEAD > diff.patch git diff --name-only "$MERGE_BASE" HEAD > files.txt printf '%s\n' "$PR_NUMBER" > pr-number.txt - name: Upload diff artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: compliance-input path: | diff.patch files.txt pr-number.txt retention-days: 1