GitHub: command injection
High8.5Published Aug 16, 2026
### Summary gh-aw already treats an attacker-written `.git/` as a threat. `setup_cache_memory_git.sh:143-147`: > Git hook files under `.git/hooks/` are preserved in the cache but are NOT > tracked by git (`git add -A` ignores `.git/`). A compromised agent run could > write executable hooks (e.g. post-checkout, post-merge) that would be restored > from cache and executed on the host runner before the AWF sandbox is > established. Remove all non-sample hook files immediately after cache restore > to prevent this. The mitigation is implemented and it works. It covers hooks only. `.git/config` is restored from the same cache, is excluded from every sanitization pass, and can define a filter driver: a shell command git runs during checkout, merge and add. The script runs checkout at `:185` and merge at `:211`. The result is that the attacker capability the hook cleanup is built to contain also gives command execution on the runner host, outside the AWF sandbox, with no execute bit required. It persists across subsequent restores from that cache namespace until the entry ages out or is replaced. Verified on `main` at commit `b2ef1f3` (2026-08-12). First verified at `bdfdb8e` (2026-0...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| github/gh-aw Product | < v0.87.0 | v0.87.0 |
Details and references
### Summary gh-aw already treats an attacker-written `.git/` as a threat. `setup_cache_memory_git.sh:143-147`: > Git hook files under `.git/hooks/` are preserved in the cache but are NOT > tracked by git (`git add -A` ignores `.git/`). A compromised agent run could > write executable hooks (e.g. post-checkout, post-merge) that would be restored > from cache and executed on the host runner before the AWF sandbox is > established. Remove all non-sample hook files immediately after cache restore > to prevent this. The mitigation is implemented and it works. It covers hooks only. `.git/config` is restored from the same cache, is excluded from every sanitization pass, and can define a filter driver: a shell command git runs during checkout, merge and add. The script runs checkout at `:185` and merge at `:211`. The result is that the attacker capability the hook cleanup is built to contain also gives command execution on the runner host, outside the AWF sandbox, with no execute bit required. It persists across subsequent restores from that cache namespace until the entry ages out or is replaced. Verified on `main` at commit `b2ef1f3` (2026-08-12). First verified at `bdfdb8e` (2026-06-24, ~v0.80.9). Every line referenced below is unchanged across that range. ### Details Locations, in `actions/setup/sh/setup_cache_memory_git.sh` unless noted: - `:143-150` hook neutralization: the comment above, and `find .git/hooks -type f ! -name '*.sample' -delete` - `:77`, `:173` `git config core.hooksPath /dev/null` - `:185` `git checkout -q "$INTEGRITY"` - `:211` `git merge "$level" -X theirs --no-edit -m "merge-from-$level" -q` - `:234`, `:240`, `:283` every sanitization pass is scoped `find . -not -path './.git/*' …` - `actions/setup/sh/commit_cache_memory_git.sh:~40` `git add -A` on save - `actions/setup/js/validate_memory_files.cjs:51-53` post-agent validation skips `.git` - `pkg/workflow/cache_integrity.go:188-223` cache key construction - `pkg/workflow/cache.go:467-490` restore-key derivation Seeding-side locations (see "Seeding" under Impact): - `pkg/workflow/claude_tools.go:396-408` unscoped `Write` appended whenever `safe-outputs` is declared; `:260-274` the scoped `Write(<cacheDir>/*)` grants the guard there fails to see - `pkg/workflow/codex_engine.go:266-270` `--dangerously-bypass-approvals-and-sandbox` when the firewall is enabled; `:284` `features.shell_tool=false` - `pkg/workflow/codex_engine.go:67`, `pkg/workflow/agentic_engine.go:150-154` shell-only scope of the bash-disable mapping - `.github/workflows/ai-moderator.lock.yml:527`, `:529`, `:537`, `:899` step order; `:926` `--mount /tmp/gh-aw:/tmp/gh-aw:rw`; `:927` the codex invocation - `.github/workflows/ai-moderator.md:216` the instruction to write into the cache-memory directory under `bash: false` **The gap.** The three sanitization passes at `:234` (symlink delete), `:240` (execute-bit strip) and `:283` (extension allowlist) are all scoped `-not -path './.git/*'`. They are working-tree controls, so the exclusion is deliberate, but it leaves `.git/config` restored byte-for-byte and outside the `GH_AW_ALLOWED_EXTENSIONS` allowlist that covers everything else the agent writes. `core.hooksPath=/dev/null` closes hooks and nothing else: a filter driver is invoked through the config, not through the hooks path. Git runs the driver via `sh -c`, so the file it operates on needs no execute bit, and the `chmod a-x` at `:240` does not help. That is why this applies on Linux GitHub-hosted runners. **The primitive.** A prior run plants: ``` # .git/config [filter "p"] smudge = sh -c '<command>' # .git/info/attributes * filter=p ``` Both paths are inside `.git/`. On the next run, `git checkout -q "$INTEGRITY"` (`:185`) materializes a file matching the attributes pattern, git invokes the smudge driver, and the command runs on the host. `git add -A` ignores `.git/`, so n
More GitHub advisories
All GitHub| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Aug 25 | gh-aw: github.event.* command injection via heredoc-blind template guardrails (MCP config, all engines) | High8.5 | No fix yet |
| Aug 16 | GitHub: improper authorization | High8.8 | v0.87.0 |
| Aug 8 | gh-aw: safe-output validator forwards undeclared agent fields to the appliers (scope escape / mass assignment) | Critical9.1 | v0.86.1 |
| Aug 7 | gh-aw: unauthenticated prompt-injection to code execution in the shipped ai-moderator workflow | Medium6.5 | v0.86.1 |
| Aug 7 | command injection in compiled workflow via unsanitized `sandbox.mcp.env` exports | Critical9.6 | v0.86.0 |
| Aug 7 | gh-aw: URL allowlist bypass via userinfo @ in the content sanitizer (exfiltration channel) | Medium6.8 | v0.86.0 |