GH-200 - Secure and optimize automation - Section 5.2

Mitigate script injection by sanitising untrusted input, applying least-privilege permissions and avoiding untrusted data in run steps.

Mitigate script injection by sanitising untrusted input before use in run steps and applying least-privilege permissions on the GITHUB_TOKEN. Recognise which expression patterns allow attacker-controlled data to reach a shell and rewrite them safely.

script injectionuntrusted inputleast privilegerun step quoting

Practice question for this objective

Free sampleSecure and optimize automationhard

A workflow triggered by the issues event greets new issues by echoing the issue title inside a run step. A security review flags that an attacker can craft a title that runs arbitrary shell commands on the runner. The team wants to keep echoing the title but remove the injection risk with the smallest change. Which change should they make?

on:
  issues:
    types: [opened]
jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - run: echo "New issue: ${{ github.event.issue.title }}"
  • AAssign the title to a step-level env variable and reference it in the run step as "$TITLE", because the value then reaches the shell as data through the environment rather than being substituted into the script text. Correct
  • BWrap the expression in single quotes, writing echo 'New issue: ${{ github.event.issue.title }}', because single quotes stop the shell from interpreting any characters inside the issue title.
  • CAdd permissions: contents: read to the job so the GITHUB_TOKEN cannot push changes, because least-privilege scoping prevents the injected commands from doing anything harmful on the runner.
  • DMove the trigger to pull_request_target instead of issues, because that event runs in a trusted context where attacker-supplied fields such as the title are sanitised by GitHub before the job starts.
Mitigate script injection by passing untrusted event fields into run steps as environment variables rather than interpolating them into the script. GitHub Actions evaluates ${{ }} expressions and substitutes the result into the run script text before any shell runs, so a malicious issue title can break out of quoting and execute commands. Binding the field to a step env variable and referencing it as a normal shell variable means the value travels as environment data, never as script source, which is the documented fix for run-step injection.

Why A is correct: Setting env: TITLE: ${{ github.event.issue.title }} passes the value as an environment variable, so the runner never inlines untrusted text into the script and the shell reads it as a quoted string.

Why B is wrong: Tempting because single quotes do disable shell expansion, but the expression is substituted into the script before the shell parses it, so the attacker's title can still close the quote and inject commands.

Why C is wrong: Tempting because least privilege is genuinely valuable, but token scoping limits API damage only, while injected shell still runs on the runner and can read other secrets or files in the job.

Why D is wrong: Tempting because pull_request_target sounds more privileged, but GitHub never sanitises event fields, and that event is actually riskier because it runs with a read-write token against untrusted input.

See more GH-200 practice questions, answers explained.

More in this domain

Back to all Secure and optimize automation objectives, or the GH-200 cert hub.

Examworthy is not affiliated with or endorsed by GitHub. Original, blueprint-aligned practice material only.