Skip to content
GitHubGHSA-796c-cr8h-rr49

gh-aw: github.event.* command injection via heredoc-blind template guardrails (MCP config, all engines)

High8.5Published Aug 25, 2026

### Summary gh-aw's template-injection guardrails skip heredoc bodies before scanning them, on the incorrect premise that heredoc content is not executed. gh-aw then emits its MCP config as an unquoted heredoc on every engine, and renders custom MCP server fields into it verbatim. A `${{ github.event.* }}` in any custom MCP field reaches the unquoted heredoc and is command-substituted by bash at runtime, giving code execution in the agent job. The build succeeds with no warning. I verified this against current `main` at commit `966ccb8` (2026-07-12). It was first verified at `bdfdb8e` (2026-06-24, ~v0.80.9). The one intervening commit that touched this area (PR #44454) widened the exposure rather than closing it (see Details). ### Details `removeHeredocContent` (`pkg/workflow/template_injection_utils.go:71-82`) strips both quoted and unquoted heredoc bodies before any guardrail scans. The premise is wrong: GitHub Actions substitutes `${{ ... }}` into the script text before bash parses it, a quoted delimiter does not stop the substitution, and a multi-line attacker value can contain a line equal to the delimiter to close the heredoc early and run the following lines. Stripping t...

GitHub advisory

Affected versions

PackageAffectedFixed in
github/gh-aw
Product
<= 0.82.4No fix yet
Details and references

### Summary gh-aw's template-injection guardrails skip heredoc bodies before scanning them, on the incorrect premise that heredoc content is not executed. gh-aw then emits its MCP config as an unquoted heredoc on every engine, and renders custom MCP server fields into it verbatim. A `${{ github.event.* }}` in any custom MCP field reaches the unquoted heredoc and is command-substituted by bash at runtime, giving code execution in the agent job. The build succeeds with no warning. I verified this against current `main` at commit `966ccb8` (2026-07-12). It was first verified at `bdfdb8e` (2026-06-24, ~v0.80.9). The one intervening commit that touched this area (PR #44454) widened the exposure rather than closing it (see Details). ### Details `removeHeredocContent` (`pkg/workflow/template_injection_utils.go:71-82`) strips both quoted and unquoted heredoc bodies before any guardrail scans. The premise is wrong: GitHub Actions substitutes `${{ ... }}` into the script text before bash parses it, a quoted delimiter does not stop the substitution, and a multi-line attacker value can contain a line equal to the delimiter to close the heredoc early and run the following lines. Stripping the body blinds all three run-step defenses: the auto-fixer that hoists expressions into env vars, and both compile validators. gh-aw emits the MCP config as an unquoted heredoc on every engine. The gateway JSON config is `cat << GH_AW_MCP_CONFIG_<hex>_EOF | "$GH_AW_NODE" ...` (`pkg/workflow/mcp_renderer.go:241`, delimiter not single-quoted). The codex TOML config is the same pattern (`pkg/workflow/codex_mcp.go:103,144,156`). Custom MCP server fields (`command`, `args`, `url`, `headers`, `env`) are written verbatim with raw `fmt.Fprintf` and no escaping (`pkg/workflow/mcp_config_custom.go`; only `secrets.*` values are rewritten). A `${{ github.event.* }}` in any of those fields lands in the unquoted heredoc and is command-substituted at runtime. The surrounding controls do not cover it. The env-indirection helper rewrites only `secrets.*`, `env.*`, and `github.workspace`, never `github.event.*` (`pkg/workflow/secret_extraction.go:219-242,324-346`). `validateExpressionSafety` scans only the markdown body, never the frontmatter `mcp-servers` config (`pkg/workflow/compiler_validators.go:32`). Intervening commit: PR #44454 ("skip heredoc content in run-block expression scan") extended the same heredoc-skipping to the one raw-text guardrail path that previously did not understand heredocs (`pkg/workflow/template_injection_validation.go:178-246`). Both scanners now deliberately skip heredoc bodies, so the exposure is marginally wider than at bdfdb8e, not narrower. ### PoC I compiled a workflow with `url: "https://api.example.com/${{ github.event.issue.title }}"` plus a `headers` entry using `github.event.issue.body`. The build succeeds and the raw expressions land in the unquoted MCP heredoc (confirmed for claude/codex/copilot). Running the compiled step through bash with an issue title of `$(echo PWNED_F11 > marker; echo INJECTED)` executed the command. ### Impact Command execution in the agent job, which holds GITHUB_TOKEN and the engine API keys. No adopter interpolates `github.event.*` into a custom MCP field today (custom MCP servers are rare), so this is a framework fix in a core trust boundary rather than an active mass-exploit. The same heredoc blind spot also covers any author `run:` step that contains a heredoc with `${{ github.event.* }}`: it compiles with no warning and the raw expression survives. CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H (8.3). Up to 10.0 (Critical) under `roles: all` with an issue/PR trigger, where any external user reaches it directly. ### Suggested fix Stop treating heredoc bodies as non-executable for expression scanning: either extract `${{ }}` from heredoc bodies into env vars, or make the regression guardrail not strip heredocs so it

CVSS 3.1
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H
Severity from
GitHub (reviewed advisory)
Weakness
CWE-78

More GitHub advisories

All GitHub
Advisory
GitHub Enterprise Server: server-side request forgery
High7.7Sep 1
GitHub Enterprise Server: race condition
High7.7Sep 1
GitHub Enterprise Server: server-side request forgery
High8.2Sep 1
Arbitrary host filesystem & Docker-socket mounts via MCP server `mounts`
Critical9.6Aug 29
Safe-output artifacts may expose CI trigger tokens
Critical9.1Aug 27
GitHub: command injection
High8.5Aug 16

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.