Skip to content

[vulnhunter] VulnHunter findings in github/gh-aw #74421

Closed

Description

@github-actions

Overview

Daily VulnHunter scan of the pre-ranked candidate scope (40 files, injection class). One finding survived falsification: the compiler's own template-injection guardrail (CTR-006) has a blind spot that lets ${{ }} expressions sneak past it when hidden inside a quoted heredoc body or a shell # comment in a run: block.

Key Issue

High — Template-injection guardrail blind to ${{ }} inside quoted heredocs / comments (CWE-94)

executableShellContent (pkg/workflow/template_injection_utils.go:166-222) is the shared pre-filter used by every CTR-006 check — validateNoTemplateInjectionFromParsed, validateNoGitHubExpressionsInRunScriptsFromParsed, and the fast-path scanRunContentExpressions (pkg/workflow/template_injection_validation.go:278, 329, 174). Before scanning a run: block for dangerous ${{ }} expressions, it blanks out (replaces with a bare newline) any text inside a bash # comment or a quoted heredoc body (<<'EOF'). That's valid Bash-expansion reasoning, but irrelevant to the actual threat: GitHub Actions performs ${{ }} substitution as raw text replacement before the shell ever runs — it has no notion of Bash quoting, comments, or heredoc state.

So a workflow run: block like:

run: |
  cat <<'EOF' > /tmp/x
  issue: ${{ github.event.issue.title }}
  EOF

compiles cleanly with no CTR-006 warning, because the expression sits inside a quoted-heredoc region that gets stripped before the scanner ever looks for ${{. At runtime, an issue title containing a line equal to the heredoc delimiter followed by a shell command breaks out of the heredoc (heredoc termination is decided by Bash only after Actions' substitution has already happened), yielding arbitrary command execution in the Actions runner with that job's GITHUB_TOKEN/secrets. The identical blind spot applies to expressions placed inside a # comment.

This is the compiler's only defense for this class (confirmed by checking pkg/workflow/markdown_security_scanner.go, which scans a different concern — Unicode/HTML abuse in imported markdown — and never touches compiled-YAML shell content), and it is unconditionally invoked for every compile (pkg/workflow/compiler.go:417-474), so there is no secondary gate that would catch this.

Verification notes

  • Reachable: yes — wired into Compiler.validateTemplateInjection, run on both the schema-validated and skipValidation paths.
  • Defense coverage: no other scanner in scope covers compiled-YAML shell injection; the exemption logic itself is the bug (sound for Bash semantics, unsound for the Actions templating layer).
  • Realism: heredocs are an idiomatic way to emit multi-line file content from a run: step, so this isn't a contrived pattern — it's the kind of script a workflow author (or an LLM-generated workflow, which this project specifically produces) would plausibly write without realizing it defeats the compiler's own safety net.
Remediation

executableShellContent should not blank out quoted-heredoc bodies or comment text before searching for ${{ }}. Two different concerns are currently conflated:

  1. "Where does Bash itself do $VAR/$() expansion" — relevant only for Bash-level sanitizer checks, if any.
  2. "Where could GitHub Actions substitute attacker data before Bash runs at all" — relevant for ${{ }} detection.

The ${{ }} scan should run over the raw, unstripped run: content (including comments and quoted-heredoc bodies); only concern (1)'s checks should use the heredoc/comment-stripped view.

Affected code:

  • pkg/workflow/template_injection_utils.go:166-222 (transformShellContent, transformShellRegions, executableShellContent)
  • pkg/workflow/template_injection_validation.go:278, 329, 174 (all three CTR-006 scan entry points)

Next actions

  • Fix executableShellContent to scan raw content for ${{ }} detection (keep stripping only for Bash-expansion-specific checks, if those exist separately).
  • Add regression tests compiling a workflow with ${{ github.event.issue.title }} inside (a) a quoted heredoc body and (b) a # comment in a run: block, asserting CTR-006 rejects both.

Full write-up saved to /tmp/gh-aw/agent/vulnhunter/out/VULN-001.md in the scan sandbox.

Generated by 🛡️ Daily VulnHunter Scan · claude · sonnet50 · ~106 AIC · ⌖ ~21 AIC · ⊞ 4K · ◷

Activity

  1. github-actions commented on Nov 4, 2026

    @github-actions
    ContributorAuthor

    🍪 Issue Monster selected this for Copilot

    I've identified this issue as a good candidate for automated resolution and requested assignment to the Copilot coding agent.

    If assignment succeeds, Copilot will analyze the issue and create a pull request with the fix.

    Om nom nom! 🍪

    🍪 Om nom nom by Issue Monster · pi · gpt50 · ~1 AIC · ⌖ ~2 AIC · ⊞ 13.1K · ◷

  2. linked a pull request that will close this issueScan raw run content for Actions expressions #74789on Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions