Summary
GitHub Security Lab documented (initial guidance August 20, 2020, updated since) a widespread GitHub Actions vulnerability class in which attacker-controlled event fields interpolated into run shell steps cause command execution. Because expressions in double-brace syntax are evaluated and substituted into the temporary shell script before the runner executes it, embedding an untrusted field such as github.event.issue.title or github.event.pull_request.head_ref directly in a run block lets the attacker break out of the intended command. An attacker who simply opens an issue or PR with a title containing a quote and a shell payload (for example a backtick command-substitution or a semicolon-curl sequence) executes arbitrary commands on the runner, reading environment variables and exfiltrating GITHUB_TOKEN and repository secrets to an external host. This is expression/script injection via untrusted github.event input, found across a large number of public-repo workflows. The fix is to never interpolate untrusted context into a shell; pass it through an intermediate quoted environment variable so it is treated as data, not script.
How to avoid it in your code
- Never interpolate github.event.* directly into run shells; assign it to an env variable and reference it quoted.
- Treat all github.event fields ending in title, body, ref, head_ref, name, label, message, or email as untrusted input.
- Set GITHUB_TOKEN permissions to read-only by default and grant write scopes only on the specific jobs that need them.
- Run static analysis (CodeQL, zizmor) on workflow files to catch expression-injection sinks before merge.
- Move logic that handles untrusted input into a pinned action or script that takes arguments, not inline templated shell.
References
- https://securitylab.github.com/resources/github-actions-untrusted-input/
- https://github.blog/security/supply-chain-security/four-tips-to-keep-your-github-actions-workflows-secure/
- https://docs.github.com/en/actions/concepts/security/script-injections
- https://semgrep.dev/docs/learn/vulnerabilities/command-injection/github-actions-injection
Related vulnerabilities
All Supply chain →- CRITICALSC-PPE-CICDSEC4-2022
Poisoned Pipeline Execution is the class of attack in which an actor with write access to source control, but no direct access to the build environment, injects attacker-controlled commands that the CI pipeline then executes with its own privileges, secrets, and tokens. Direct PPE (D-PPE) modifies the CI configuration file itself (for example .github/workflows, .gitlab-ci.yml, or a Jenkinsfile) by pushing to an unprotected branch or opening a pull request, so the new pipeline steps run on trigger. Indirect PPE (I-PPE) instead poisons files the pipeline already references, such as a Makefile, test harness, build script, or linter config, when the config is protected but the referenced code is not. Public PPE (3PE) abuses public and open-source repositories that run unreviewed code from anonymous fork pull requests, frequently via the dangerous pull_request_target trigger that grants the fork workflow access to repository secrets. The pattern is catalogued as CICD-SEC-4 in the OWASP Top 10 CI/CD Security Risks (published September 2022) and in Cider/Legit Security research, with real cases including public-repo PPE in popular projects and GitHub Actions workflows abused for cryptocurrency mining.
- HIGHCVE-2026-75911
CodeWhale: Project config `allow_shell` override enables arbitrary shell command execution via cloned repository
- HIGHCVE-2026-75858
CodeWhale: rlm_eval auto-approves arbitrary Python execution, bypassing the user's approval policy (RCE)
- HIGHCVE-2026-73222
Claude Code Templates: Unauthenticated OS command injection (RCE) in Claude Code Studio server (--studio)
- CRITICALCVE-2026-62681
Orval: RCE via OpenAPI path -> unescaped request-URL template literal (backtick breakout)
- CRITICALCVE-2026-62682
Orval: RCE via servers[].url -> unescaped request-URL template literal (with getBaseUrlFromSpecification)