Skip to content

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

Description

@github-actions

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

  1. A developer runs gh aw update on a workflow whose source: (or a redirect it follows) points at an attacker-controlled repo/file.
  2. The primary gh api contents call returns an auth error, so control enters the git fallback (download_workflow.go:189-192).
  3. The remote redirect: frontmatter is parsed by parseSourceSpec, which joins the path segments verbatim with no .md requirement and no leading-dash check.
  4. 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 ·

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions