SEC012 — github-env-injection¶
Severity: blocker · Category: Security
What it checks¶
Flags a run: step that writes to GITHUB_ENV or GITHUB_PATH using
attacker-controllable input (the same untrusted-context list as
SEC004), inside a workflow triggered by pull_request_target,
workflow_run, or issue_comment.
Why it matters¶
GITHUB_ENV and GITHUB_PATH let a step set environment variables or
prepend to PATH for every step that runs after it, in the same job. In
a privileged, dangerous-trigger workflow, an attacker who controls the
value being written can set arbitrary environment variables (for example
LD_PRELOAD, to hijack dynamic linking in a later step) or add a
directory to PATH ahead of the real system tools, letting them shadow
a command like ssh or git with their own script.
Examples¶
Flagged — a comment body, fully attacker-controlled, is written
straight into GITHUB_ENV in a privileged trigger context:
on: pull_request_target
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "NOTE=${{ github.event.comment.body }}" >> "$GITHUB_ENV"
- run: ./build.sh # NOTE is now attacker-controlled in this step's env
Fixed — don't derive GITHUB_ENV/GITHUB_PATH values from
untrusted input at all; use GITHUB_OUTPUT (scoped to the producing
step, not the whole job) if state needs to pass between steps, or stick
to literal, trusted values:
on: pull_request_target
jobs:
build:
runs-on: ubuntu-latest
steps:
- id: note
run: echo "value=fixed-note" >> "$GITHUB_OUTPUT"
- run: echo "Note was ${{ steps.note.outputs.value }}"
Suppressing¶
The finding is reported on the run: step doing the write:
- run: echo "NOTE=${{ github.event.comment.body }}" >> "$GITHUB_ENV" # vlotpipe: ignore[SEC012]
Only if the value is sanitized/validated against a strict allowlist before being written — and even then, prefer fixing the pattern over suppressing it, since this is a blocker-severity finding for a reason.