Summary
A single confirmed, exploitable finding survived VulnHunter's falsification pass: an argument-injection (CWE-88) flaw in the git archive fallback used by gh aw update. Attacker-controlled workflow redirect:/source: frontmatter can inject a --output=<path> flag into the git command line, causing an arbitrary local file write.
All other candidates (zip/tar extraction, remote import-cache path handling, git-clone sparse checkout, SSRF in contents-API/raw-URL construction, and the entire crypto/logic class) were traced and eliminated — they are already guarded.
VULN-001 — Argument injection in git-archive update fallback
- Affected file/function:
pkg/cli/download_workflow.go:38, downloadWorkflowContentViaGit
- Type / severity: CWE-88 argument injection → arbitrary local file write. Severity: Medium (remote-triggered, but gated behind the auth-error fallback branch).
Attacker path / preconditions
- A developer runs
gh aw update on a workflow whose source: (or a redirect it follows) points at an attacker-controlled repo/file.
- The primary
gh api contents call returns an auth error, so control enters the git fallback (download_workflow.go:189-192).
- The remote
redirect: frontmatter is parsed by parseSourceSpec, which joins the path segments verbatim with no .md requirement and no leading-dash check.
- A redirect such as
victim/repo/--output=/home/dev/.ssh/authorized_keys@<sha> yields Path = "--output=/home/dev/.ssh/authorized_keys", passed as a trailing positional arg to git archive. git interprets it as a flag → the tar output is written to the attacker-chosen path.
Why it is credible after falsification
The sibling implementation in the parser package (pkg/parser/remote_download_file.go:332) was hardened for exactly this: it validates ref and path via gitutil.ValidateGitRef + gitutil.ValidateGitPath (which reject a leading -) and inserts a -- end-of-options separator before path:
cmd := exec.CommandContext(ctx, "git", "archive", "--remote="+repoURL, ref, "--", path)
The CLI copy omits both defenses:
cmd := exec.CommandContext(ctx, "git", "archive", "--remote="+repoURL, ref, path)
git archive keeps parsing ---prefixed tokens after the tree-ish, so --output=<path> (and other flags) reach git. ref is a resolved 40-char SHA on this path (safe); only path is injectable.
Remediation
Mirror the parser fix in downloadWorkflowContentViaGit:
- Call
gitutil.ValidateGitRef(ref) and gitutil.ValidateGitPath(path) before building the command, returning an error on rejection.
- Add the
"--" end-of-options separator before path: exec.CommandContext(ctx, "git", "archive", "--remote="+repoURL, ref, "--", path).
This eliminates the class (leading-dash args + option parsing) rather than just the --output payload.
Data-flow trace
remote file content
-> extractRedirectFromContent (pkg/cli/update_redirects.go:140)
-> normalizeRedirectToSourceSpec (pkg/cli/update_redirects.go:159)
-> parseSourceSpec (pkg/cli/spec.go:448; path join at :462, no validation)
-> SourceSpec.Path
-> downloadWorkflowContentFn (pkg/cli/update_redirects.go:72)
-> downloadWorkflowContent (auth-error) (pkg/cli/download_workflow.go:189-192)
-> downloadWorkflowContentViaGit (pkg/cli/download_workflow.go:24)
-> exec git archive ... ref path (pkg/cli/download_workflow.go:38) [SINK]
gitutil.ValidateGitPath (pkg/gitutil/gitutil.go:94-109) already rejects leading -, absolute paths, and ..; it is simply not called on this path.
Candidates eliminated after falsification
- Zip/tar artifact extraction (
logs_download.go, fileutil.ExtractFileFromTar): filepath.Clean + .. reject + destination-prefix check + LimitReader → zip-slip safe.
- Remote import-cache path → filesystem: guarded by
validatePathComponents (rejects ../abs) + sanitizePath.
downloadWorkflowContentViaGitClone (download_workflow.go:62): rejects absolute/../ paths, re-validates with fileutil.ValidatePathWithinBase; path is used as a sparse-checkout pattern / file target, not an exec flag.
- SSRF via
buildContentsAPIPath / raw-URL Sprintf: path segments url.PathEscape'd, ref url.QueryEscape'd, host constrained by the isGitHubHost allowlist.
- Crypto/logic (LOG) class: no
InsecureSkipVerify, no MD5/SHA1 used for security, math/rand only in Monte-Carlo forecasting and PR-scheduling jitter (non-security contexts).
Generated by 🛡️ Daily VulnHunter Scan · sonnet46 · 544.6 AIC · ⌖ 38.8 AIC · ⊞ 4.4K · ◷
Summary
A single confirmed, exploitable finding survived VulnHunter's falsification pass: an argument-injection (CWE-88) flaw in the
git archivefallback used bygh aw update. Attacker-controlled workflowredirect:/source:frontmatter can inject a--output=<path>flag into the git command line, causing an arbitrary local file write.All other candidates (zip/tar extraction, remote import-cache path handling, git-clone sparse checkout, SSRF in contents-API/raw-URL construction, and the entire crypto/logic class) were traced and eliminated — they are already guarded.
VULN-001 — Argument injection in git-archive update fallback
pkg/cli/download_workflow.go:38,downloadWorkflowContentViaGitAttacker path / preconditions
gh aw updateon a workflow whosesource:(or a redirect it follows) points at an attacker-controlled repo/file.gh apicontents call returns an auth error, so control enters the git fallback (download_workflow.go:189-192).redirect:frontmatter is parsed byparseSourceSpec, which joins the path segments verbatim with no.mdrequirement and no leading-dash check.victim/repo/--output=/home/dev/.ssh/authorized_keys@<sha>yieldsPath = "--output=/home/dev/.ssh/authorized_keys", passed as a trailing positional arg togit archive.gitinterprets it as a flag → the tar output is written to the attacker-chosen path.Why it is credible after falsification
The sibling implementation in the parser package (
pkg/parser/remote_download_file.go:332) was hardened for exactly this: it validates ref and path viagitutil.ValidateGitRef+gitutil.ValidateGitPath(which reject a leading-) and inserts a--end-of-options separator beforepath:The CLI copy omits both defenses:
git archivekeeps parsing---prefixed tokens after the tree-ish, so--output=<path>(and other flags) reach git.refis a resolved 40-char SHA on this path (safe); onlypathis injectable.Remediation
Mirror the parser fix in
downloadWorkflowContentViaGit:gitutil.ValidateGitRef(ref)andgitutil.ValidateGitPath(path)before building the command, returning an error on rejection."--"end-of-options separator beforepath:exec.CommandContext(ctx, "git", "archive", "--remote="+repoURL, ref, "--", path).This eliminates the class (leading-dash args + option parsing) rather than just the
--outputpayload.Data-flow trace
gitutil.ValidateGitPath(pkg/gitutil/gitutil.go:94-109) already rejects leading-, absolute paths, and..; it is simply not called on this path.Candidates eliminated after falsification
logs_download.go,fileutil.ExtractFileFromTar):filepath.Clean+..reject + destination-prefix check +LimitReader→ zip-slip safe.validatePathComponents(rejects../abs) +sanitizePath.downloadWorkflowContentViaGitClone(download_workflow.go:62): rejects absolute/../paths, re-validates withfileutil.ValidatePathWithinBase; path is used as a sparse-checkout pattern / file target, not an exec flag.buildContentsAPIPath/ raw-URLSprintf: path segmentsurl.PathEscape'd, refurl.QueryEscape'd, host constrained by theisGitHubHostallowlist.InsecureSkipVerify, no MD5/SHA1 used for security,math/randonly in Monte-Carlo forecasting and PR-scheduling jitter (non-security contexts).