Skip to main content
FieldValue
CategoryCICD-SEC-4
SeverityHIGH
OWASPCICD-SEC-4: Poisoned Pipeline Execution
Auto-fix

What the check does

Flags a run: step that writes to $GITHUB_ENV or $GITHUB_PATH with a value derived from untrusted event data — either:
  • directly, echo "X=${{ github.event.issue.title }}" >> $GITHUB_ENV, or
  • laundered through an env: variable that was assigned untrusted data, echo "X=$TITLE" >> $GITHUB_ENV where TITLE: ${{ github.event.pull_request.title }}.

Why it matters

$GITHUB_ENV and $GITHUB_PATH set the environment and PATH for every subsequent step in the job. An attacker who controls the written value can:
  • inject loader/runtime variables (LD_PRELOAD, NODE_OPTIONS, BASH_ENV, …) that execute code in later steps, or
  • prepend a directory to PATH so a later make/npm/git invocation runs their binary.
Crucially, this is not fixed by the usual shell-injection mitigation. Moving the value into an env: variable and using $TITLE protects the current shell, but the bytes still land in the environment file — so the later-step poisoning remains. That’s why this is its own rule, distinct from CICD-SEC-4 shell injection.

Vulnerable example

on: pull_request_target
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - env:
          TITLE: ${{ github.event.pull_request.title }}
        run: echo "PR_TITLE=$TITLE" >> "$GITHUB_ENV"   # ← poisons later steps

Safe alternative

Don’t persist untrusted input into the environment files. Keep it in a step-scoped env: and consume it only in the step that needs it, after validating/allowlisting:
      - env:
          TITLE: ${{ github.event.pull_request.title }}
        run: |
          # use "$TITLE" locally; do NOT write it to $GITHUB_ENV/$GITHUB_PATH
          echo "Title length: ${#TITLE}"