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)
This commit is contained in:
Félix Malfait
2026-03-02 09:20:03 +01:00
committed by GitHub
parent 1db2a40961
commit 68d2297338
+5 -2
View File
@@ -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,