name: Swedish Accounting Compliance Review # Stage 2 of the fork-safe compliance review (stage 1 is swedish-compliance-diff.yml). # # SECURITY: this is the privileged half: it has the AWS Bedrock secrets and a # write token. It is triggered by `workflow_run` (NOT pull_request_target) and # checks out ONLY the base repo, so it never executes fork PR code. The untrusted # input (the PR diff) arrives as a downloaded artifact and is fed to the model # as DATA: never run. This is the pattern GitHub recommends instead of # `pull_request_target` + checking out the PR head. on: workflow_run: # Must match the `name:` field in swedish-compliance-diff.yml exactly. # A rename there silently stops this trigger from firing on all subsequent PRs. workflows: ["Compliance diff"] types: [completed] permissions: contents: read actions: read # required to download the artifact from the triggering run pull-requests: write # required to post the review comment jobs: review: runs-on: ubuntu-latest timeout-minutes: 10 # Only act on PR-triggered diffs that actually produced an artifact. if: > github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' steps: # Base repo only: the TRUSTED copy of the script and .claude/skills/. # persist-credentials: false, no later step needs git push creds, so don't # leave the token in .git/config for the steps that handle untrusted input. - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 with: node-version: 20 - name: Download diff artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: compliance-input # Unpacked OUTSIDE the checkout, not into the workspace root. # # This artifact is built from a fork's PR head, so its contents are # attacker-influenced. Extracted over the workspace, an entry named # `scripts/swedish-compliance-review.mjs` would overwrite the trusted # script this job is about to run, with the AWS secrets and a write # token already in scope. Stage 1 only ever writes three fixed # filenames, and its workflow definition comes from the base repo even # for fork PRs, so that is not reachable today: this keeps it # unreachable if stage 1 ever grows a filename derived from PR # content. Flagged by CodeQL as actions/artifact-poisoning. path: ${{ runner.temp }}/compliance-input run-id: ${{ github.event.workflow_run.id }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: Resolve PR number id: pr run: | set -euo pipefail NUM=$(cat "$RUNNER_TEMP/compliance-input/pr-number.txt") # Guard: pr-number.txt must be a plain integer (artifact is untrusted input). if ! [[ "$NUM" =~ ^[0-9]+$ ]]; then echo "Refusing to continue: pr-number.txt is not a number" >&2 exit 1 fi echo "number=$NUM" >> "$GITHUB_OUTPUT" - name: Install Anthropic Bedrock SDK # Installed ONE DIRECTORY ABOVE the checkout on purpose. Running this # inside the repo makes npm re-resolve the whole dependency tree from # package.json (--no-package-lock throws away the pinned resolutions), # and that re-resolution dies on an unrelated floating peer conflict # (@hookform/resolvers -> valibot), which silently killed this review on # every PR. Out of tree npm resolves this one package and nothing else, # so an unrelated peer conflict can never take the compliance gate down # again. Node still finds it: ESM bare specifiers walk up the parent # directories' node_modules. NODE_PATH is not an alternative here, the # ESM loader ignores it (CommonJS only). # Version tracks the exact package.json pin: 0.32.0 broke Bedrock # streaming in prod (DECISIONS.md 2026-07-08) and check:guards enforces # 0.29.1 repo-wide, so this job must not be the one place running an # unvetted build. Pinned exact + --ignore-scripts because this is the # privileged job (write token in env): no floating @latest, and no # dependency lifecycle scripts may execute here. run: npm install --prefix "$GITHUB_WORKSPACE/.." --no-save --no-package-lock --ignore-scripts @anthropic-ai/bedrock-sdk@0.29.1 - name: Run compliance review env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_REGION || 'eu-north-1' }} REVIEW_MODEL: eu.anthropic.claude-sonnet-5 # Two-stage mode: read the diff from the artifact instead of git-diffing. DIFF_FILE: ${{ runner.temp }}/compliance-input/diff.patch FILES_FILE: ${{ runner.temp }}/compliance-input/files.txt run: node scripts/swedish-compliance-review.mjs - name: Assert review produced output # This job once produced no compliance signal for 10 consecutive PR # runs (the in-tree npm install died on an unrelated peer conflict, see # the install step above) and nobody noticed: workflow_run-triggered # jobs do not appear on the PR checks list, so a red or silently empty # run is invisible from the PR. Fail loudly whenever the review script # finishes without writing a non-empty review.md, so "no output" can # never again pass as a green run. run: | set -euo pipefail if [ ! -s review.md ]; then echo "::error::Compliance review produced no output (review.md missing or empty)" exit 1 fi - name: Find previous compliance comment uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 id: find-comment with: issue-number: ${{ steps.pr.outputs.number }} comment-author: 'github-actions[bot]' body-includes: '' - name: Post or update PR comment uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 with: issue-number: ${{ steps.pr.outputs.number }} comment-id: ${{ steps.find-comment.outputs.comment-id }} body-path: review.md edit-mode: replace