From b6f8688df83ed16190d87ac266214fcfdfdccee7 Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Sun, 24 May 2026 15:31:16 -0500 Subject: [PATCH] fix(ci): route github.event.pull_request.body through env: to avoid shell injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-existing latent bug surfaced by PR #17's CI run. The Decide step's inline `${{ github.event.pull_request.body }}` interpolation pastes arbitrary PR-body text directly into a bash script. When the body contains shell metacharacters (parens, pipes, backticks, redirections, etc.) the resulting script either errors out at YAML-load time OR — worse — executes attacker-controlled shell. This bit PR #17 with a real syntax error ("unexpected token `('") because the PR body contains parenthesized phrases like "(was: 10-minute timeout)". Every Decide-step run in this repo has been one badly-formatted PR body away from breaking the gate. Fix: forward `github.event.pull_request.body` and `github.event.pull_request.head.repo.fork` through the step's `env:` block as `PR_BODY` and `PR_IS_FORK`, then quote them in shell (`"$PR_BODY"`, `"$PR_IS_FORK"`). The env-var path leaves the values as plain strings rather than syntactically embedded code, which is the standard GitHub Actions hardening pattern (see GitHub's "Security hardening for GitHub Actions" → "Using a third-party action"). Same change in both workflows; ~9 LOC each. This fix is technically beyond Brief 1's scope (which targeted only Vercel-bypass plumbing) but is added in this convoy because the bug actively blocks Brief 1's success criterion from being validated on PR #17. Documented in the convoy file's "Anything flagged but not acted on" follow-up pass. Co-authored-by: Cursor --- .github/workflows/preview-smoke.yml | 13 +++++++++++-- .github/workflows/visual-diff.yml | 9 +++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/preview-smoke.yml b/.github/workflows/preview-smoke.yml index 976882b..8226e9d 100644 --- a/.github/workflows/preview-smoke.yml +++ b/.github/workflows/preview-smoke.yml @@ -40,11 +40,20 @@ jobs: steps: - name: Decide id: check + env: + # Route PR body + fork flag through env vars instead of inline + # ${{ }} interpolation. Direct ${{ github.event.pull_request.body }} + # in a shell command pastes arbitrary user-controlled text (parens, + # backticks, pipes, heredocs) directly into the script — both a + # syntax-error risk AND a shell-injection vector. Quoting the env + # vars below makes both safe. + PR_BODY: ${{ github.event.pull_request.body }} + PR_IS_FORK: ${{ github.event.pull_request.head.repo.fork }} run: | - if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then + if [[ "$PR_IS_FORK" == "true" ]]; then echo "should_run=false" >> $GITHUB_OUTPUT echo "::notice::Smoke skipped on fork PR (bypass secret unavailable to forks)" - elif echo "${{ github.event.pull_request.body }}" | grep -qE 'pipeline:.*skip.*\bsmoke\b'; then + elif echo "$PR_BODY" | grep -qE 'pipeline:.*skip.*\bsmoke\b'; then echo "should_run=false" >> $GITHUB_OUTPUT echo "::notice::Smoke skipped via pipeline directive" else diff --git a/.github/workflows/visual-diff.yml b/.github/workflows/visual-diff.yml index 8a12afa..75e3578 100644 --- a/.github/workflows/visual-diff.yml +++ b/.github/workflows/visual-diff.yml @@ -36,11 +36,16 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: check + env: + # See preview-smoke.yml for the rationale: ${{ github.event.* }} + # inlined into shell is a syntax-error + injection vector. + PR_BODY: ${{ github.event.pull_request.body }} + PR_IS_FORK: ${{ github.event.pull_request.head.repo.fork }} run: | - if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then + if [[ "$PR_IS_FORK" == "true" ]]; then echo "should_run=false" >> $GITHUB_OUTPUT echo "::notice::Visual diff skipped on fork PR (bypass secret unavailable to forks)" - elif echo "${{ github.event.pull_request.body }}" | grep -qE 'pipeline:.*skip.*\bvisual\b'; then + elif echo "$PR_BODY" | grep -qE 'pipeline:.*skip.*\bvisual\b'; then echo "should_run=false" >> $GITHUB_OUTPUT echo "::notice::Visual diff skipped via pipeline directive" else