From 68d22973381410dd3c2268d14ce2c234efaa607b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Mon, 2 Mar 2026 09:20:03 +0100 Subject: [PATCH] Fix expression injection in cross-repo GitHub Actions workflow (#18316) ## Summary - Fixes a script injection vulnerability in the `claude-cross-repo` job's `actions/github-script` step where `${{ steps.prompt.outputs.repo }}` and `${{ steps.prompt.outputs.issue_number }}` were interpolated directly into JavaScript string literals. A crafted dispatch payload could inject arbitrary JavaScript with access to `secrets.TWENTY_DISPATCH_TOKEN`. - Values are now passed via `env:` and accessed through `process.env`, which treats them as data rather than code. ## Context Motivated by the [hackerbot-claw campaign](https://www.stepsecurity.io/blog/hackerbot-claw-github-actions-exploitation) which exploited similar `${{ }}` expression injection patterns in workflows at Microsoft, DataDog, and CNCF projects. The broader analysis found that our workflow is **not vulnerable** to the primary attack vector (Pwn Request via `pull_request_target` + untrusted checkout), and `claude-code-action` already gates on write access internally. This expression injection in the cross-repo dispatch job was the only concrete vulnerability identified. ## Test plan - [ ] Verify the `claude-cross-repo` job still posts comments back to the source issue after a dispatch run - [ ] Confirm `TARGET_REPO` and `TARGET_ISSUE` env vars are correctly resolved from step outputs Made with [Cursor](https://cursor.com) --- .github/workflows/claude.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index bc77d328c1..37747d4083 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -160,11 +160,14 @@ jobs: - name: Post response to source issue if: always() uses: actions/github-script@v7 + env: + TARGET_REPO: ${{ steps.prompt.outputs.repo }} + TARGET_ISSUE: ${{ steps.prompt.outputs.issue_number }} with: github-token: ${{ secrets.TWENTY_DISPATCH_TOKEN }} script: | - const [owner, repo] = '${{ steps.prompt.outputs.repo }}'.split('/'); - const issueNumber = parseInt('${{ steps.prompt.outputs.issue_number }}', 10); + const [owner, repo] = process.env.TARGET_REPO.split('/'); + const issueNumber = parseInt(process.env.TARGET_ISSUE, 10); await github.rest.issues.createComment({ owner,