From 3df65ca63b29d8cd355e35dde9d05257b308f3bf Mon Sep 17 00:00:00 2001 From: Will Griffin Date: Thu, 30 Jul 2026 08:06:39 -0600 Subject: [PATCH] ci: pass untrusted pull-request fields through env in changeset-check `${{ }}` expansion happens before the shell ever reads the script, so interpolating github.event.pull_request.title and .head.ref into the `Check for changeset` step's inline run: block made a crafted PR title or branch name execute as shell source on the runner. Pass both through the step's env: block instead, per GitHub's documented mitigation. Every existing use already quotes "$PR_TITLE" / "$PR_BRANCH", so the gate's skip/pass/fail decisions are unchanged. Also quote $GITHUB_ENV in the pnpm store step so actionlint reports no findings for this file (SC2086). --- .github/workflows/changeset-check.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/changeset-check.yml b/.github/workflows/changeset-check.yml index e22d948..fb3c77b 100644 --- a/.github/workflows/changeset-check.yml +++ b/.github/workflows/changeset-check.yml @@ -36,7 +36,7 @@ jobs: - name: Get pnpm store directory shell: bash run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV" - name: Setup pnpm cache uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 @@ -52,9 +52,11 @@ jobs: - name: Check for changeset env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Attacker-controlled text. Passed through the environment so it is + # never expanded into this step's shell source. + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BRANCH: ${{ github.event.pull_request.head.ref }} run: | - PR_TITLE="${{ github.event.pull_request.title }}" - PR_BRANCH="${{ github.event.pull_request.head.ref }}" BASE_SHA="${{ github.event.pull_request.base.sha }}" HEAD_SHA="${{ github.event.pull_request.head.sha }}" MERGE_BASE=$(git merge-base "$BASE_SHA" "$HEAD_SHA")