Description
Issue and PR titles or bodies, comments, commit messages and author information can be controlled by external users. Inserting these values into a run command through a ${{ }} expression substitutes them into the code before the shell executes it, creating a command-injection risk. Quoting the expression alone is insufficient.
Potential impact
- Injected commands can access runner files and the tokens or secrets available to the job.
- Depending on those permissions, an attacker may alter artifacts or misuse repository operations.
Remediation
- Do not interpolate external values into inline shell code. Pass needed values through the step’s
envand use quoted shell variables such as"$VALUE". Do not evaluate the value again withevalor similar mechanisms. - Validate expected formats, keep command arguments separate, and restrict
GITHUB_TOKENand secret access to what the job needs. - Do not check out or execute untrusted PR code in privileged workflows such as
pull_request_target.
Examples
The first example inserts the PR body into a command. The second is a separate example of fixed repository test commands, assuming trusted base-branch code and the required test script.
Before
name: Pull Request Workflow
on:
pull_request_target:
types:
- opened
jobs:
process_pull_request:
runs-on: ubuntu-latest
steps:
- name: Echo Pull Request Body
run: |
echo "Pull Request Body: ${{ github.event.pull_request.body }}"
The PR body becomes part of the shell code. Putting attacker-controlled text inside double quotes does not remove the injection risk.
After
name: check-go-coverage
on:
pull_request_target:
branches: [master]
jobs:
coverage:
name: Check Go coverage
runs-on: ubuntu-latest
steps:
- name: Checkout Source
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go 1.22.x
uses: actions/setup-go@v5
with:
go-version: 1.22.x
- name: Run test metrics script
id: testcov
run: |
make test-coverage-report | tee test-results
echo "coverage=$(cat test-results | grep "Total coverage: " test-results | cut -d ":" -f 2 | bc)" >> $GITHUB_ENV
These commands do not directly insert the PR body. If external values are added later, pass them as data and continue to keep untrusted code out of privileged execution.