GitHub Actions Shell Injection via Unsanitized Issue Metadata in Workflow Templates
GitHub Actions workflows that directly interpolate untrusted issue fields (title, body, labels) into shell `run` blocks using template expressions are vulnerable to command injection. This PoC demonstrates that unauthenticated attackers can trigger arbitrary code execution and exfiltrate secrets by crafting malicious issue titles.
Affected
Vulnerability Description
This vulnerability is a shell command injection in GitHub Actions workflows caused by unsafe template variable resolution. The root cause is embedding GitHub Actions context variables (e.g., ${{ github.event.issue.title }}) directly into bash script blocks without proper escaping or sanitization. The GitHub Actions template engine resolves these expressions at workflow compilation time, substituting the raw untrusted value as literal bash code before the runner executes the script. Because the substitution occurs within double-quoted strings, bash interprets subshell syntax like $(command) and backticks `...`. An attacker with no repository privileges can trigger the issues: [opened] event by creating a public issue with a crafted title, causing arbitrary code execution in the workflow runner context—including access to all secrets available to that workflow.
Proof-of-Concept Significance
This PoC is significant because it demonstrates a reliable, low-barrier attack vector against GitHub Actions infrastructure. The trigger requires only the ability to create an issue (often public-by-default), meaning external, unauthenticated users can exploit it. The PoC reliably proves that workflow secrets (e.g., DISCORD_WEBHOOK_URL, API keys, deployment credentials) can be exfiltrated by injecting subshell commands into the issue title. Since GitHub Actions templates are resolved at compilation time—not runtime—traditional shell escaping techniques fail. This makes the vulnerability particularly insidious: developers may incorrectly assume that GitHub Actions context variables are "safe" without explicit sanitization.
Detection Guidance
Workflow-Level Indicators:
- Audit
.github/workflows/YAML files for patterns:run: |orrun: >blocks that directly reference${{ github.event.issue.* }},${{ github.event.pull_request.* }}, or other user-controllable context variables without shell quoting (e.g.,${{ toJSON(...) }}or environment variable indirection). - Flag workflows where context variables appear in variable assignments, command substitutions, or within conditional expressions without
::set-outputor::set-envcommands (which have additional protections).
Runtime Indicators (in runner logs):
- Unexpected command execution or subshell invocations in workflow logs (check for
$()or backtick evaluation inPrepare Notificationor similar steps). - Environment variable exfiltration attempts (e.g.,
curl,wget, or file reads targetingenvor.envfiles). - Anomalous HTTP POST requests to external domains (e.g., webhook URL changes or unauthorized webhook calls).
SAST/Linting:
- Use GitHub Actions linters (e.g.,
github-actions-linter) to flag direct context variable interpolation inrunblocks. - Implement custom rules in YAML parsers to detect
${{ github.event.issue.patterns within shell contexts.
Mitigation Steps
Immediate Patch (Recommended):
- Move untrusted context variables into GitHub Actions environment variables using
env:blocks, which are passed safely and do not undergo template expansion within their values:- name: Prepare Notification env: ISSUE_TITLE: ${{ github.event.issue.title }} run: | # ISSUE_TITLE is now a shell environment variable, not injected at template-expansion time DESCRIPTION="$ISSUE_TITLE" - Use
toJSON()wrapper for complex objects to enforce strict escaping:DESCRIPTION: ${{ toJSON(github.event.issue.title) }} - Implement explicit input validation in the workflow or downstream code; use shell parameter expansion modifiers to strip dangerous characters if injection patterns are detected.
Workarounds (if patching is delayed):
- Disable the
issues: [opened]trigger and restrict workflows topushorpull_requestevents on trusted branches only. - Require issue creation/editing to be restricted to organization members (repository settings).
- Add a manual approval step before the vulnerable workflow runs.
Long-term Security:
- Enforce a code review policy for all GitHub Actions workflows, specifically checking for context variable interpolation in
runblocks. - Use GitHub Advanced Security (code scanning) to flag these patterns automatically.
- Rotate any secrets (Discord webhook URLs, API keys) that were exposed prior to patching.
Risk Assessment
Likelihood of Exploitation in the Wild: High. The attack requires minimal effort (crafting an issue title), no authentication, and succeeds reliably across any repository using similar workflow patterns. Public GitHub repositories with automated workflows are high-value targets. Automated scanning tools can easily enumerate vulnerable workflows across GitHub (since workflows are often public).
Threat Actor Interest: Very High. This vulnerability enables:
- Supply chain attacks: Injecting malicious code into CI/CD pipelines of downstream users or projects.
- Secret exfiltration: Stealing API keys, deployment tokens, and webhook URLs for lateral movement or further compromise.
- Supply chain poisoning: Modifying build outputs or deployments during CI/CD.
Given the maturity of GitHub Actions as a CI/CD platform and its widespread adoption, this class of vulnerability is particularly attractive to organized threat actors targeting software development infrastructure.
Sources