VulnHunter scan (injection + navigation phases) of the repository snapshot. One finding survived falsification; the token-handling and SSRF surface was audited and found safe.
VULN-001 — Git argument injection via unvalidated ref/path in remote import fallbacks
Type: Argument injection into git subprocess (CWE-88) → arbitrary file write / DoS
Severity: Medium (High in token-less CI / unauthenticated contexts, where the fallback path is the norm)
Affected code
pkg/parser/remote_download_file.go
downloadFileViaGit L321: exec.CommandContext(ctx, "git", "archive", "--remote="+repoURL, ref, path)
downloadFileViaGitClone L408: exec.CommandContext(ctx, "git", "-C", tmpDir, "checkout", ref)
pkg/parser/remote_resolve_sha.go
resolveRefToSHAViaGit L41 / L46: git ls-remote <url> <ref> and git ls-remote <url> <prefix>+ref
Attacker path
- Source: a workflowspec
owner/repo/path@<ref>. The ref (text after @) and path are attacker-controlled via a workflow's imports: frontmatter — a third-party workflow that a victim compiles/installs (gh aw add, gh aw compile). Parsed by parseRemoteOrigin (pkg/parser/import_remote.go:41), parseWorkflowSpecParts (pkg/parser/remote_workflow_spec.go:110), and parseWorkflowSpec (pkg/cli/spec.go:332).
- No validation: only
owner/repo are validated (spec.go:374). ref and path get no leading-dash / character check.
- Sink reached via the auth-error fallback: when
gitutil.IsAuthError is true (no GH_TOKEN, "unauthorized", "forbidden", "permission denied", "saml enforcement" — see pkg/gitutil/gitutil.go:34), the code shells out to git with ref/path in option-parsed positional slots and no -- end-of-options separator (verified absent).
- Impact:
git archive --remote=<url> <ref> <path> — a ref/path like --output=/victim/path makes git-archive write its archive to an attacker-chosen file → arbitrary file write/overwrite. git ls-remote <url> <ref> — a ref like --upload-pack=... is option-parsed → command/behavior control / DoS.
Why it is credible after falsification
- Sinks use
exec.CommandContext (no shell), so the existing #nosec G204 note is right about shell injection but does not cover argument injection — a ref starting with - is an option, not a value.
IsAuthError triggers on the very common token-less / unauthorized case, so the fallback is readily reachable (private repo, GHE host, or failing raw URL).
- SHA validation runs on the result of resolution, not the input
ref; any non-SHA ref flows to git unchanged.
- The project already defends against this exact class elsewhere, proving scope and gap:
pkg/workflow/name_validation.go:70 (rejects leading - "to prevent argument injection"), pkg/workflow/npm_validation.go:93 (npm view -- pkg), pkg/cli/update_command.go:384 (--), pkg/workflow/pip_validation.go:66. The git fallbacks use none of these.
Remediation
- Reject
ref/path beginning with - at parse time (mirror name_validation.go / pip_validation.go).
- Add
-- end-of-options on every git call taking a user-derived positional (git ls-remote <url> -- <ref>; for archive, -- before <path> and validate ref; avoid --output=-style values).
- Prefer resolving refs via the GitHub API and passing only validated 40-hex SHAs to
git. Centralize a git-ref allowlist (no leading -, no .., no control chars) applied in parseWorkflowSpecParts, parseRemoteOrigin, and parseWorkflowSpec.
Areas audited and found safe (no finding)
- Generic URL import fetch / SSRF & token leak: scheme restricted to http/https (
pkg/cli/spec.go:299); Authorization: Bearer attached only to an exact GitHub host allowlist (attachImportAuthHeader), and Go's default client strips auth on cross-host redirects. The imports: path never accepts an http(s):// URL, so a third-party workflow cannot redirect fetches to an attacker host.
- Token/secret handling: tokens are read from env /
gh auth token, never logged (only var names / redacted prefixes), and only sent to the GitHub host allowlist. secrets.go values are used solely for MCP env injection.
Scan scope: injection + navigation phases against the highest-risk entry points (remote import/download, ref resolution, token handling). Not an exhaustive whole-repo scan.
Generated by 🛡️ Daily VulnHunter Scan · sonnet46 · 470.6 AIC · ⌖ 38.2 AIC · ⊞ 4K · ◷
VulnHunter scan (injection + navigation phases) of the repository snapshot. One finding survived falsification; the token-handling and SSRF surface was audited and found safe.
VULN-001 — Git argument injection via unvalidated
ref/pathin remote import fallbacksType: Argument injection into
gitsubprocess (CWE-88) → arbitrary file write / DoSSeverity: Medium (High in token-less CI / unauthenticated contexts, where the fallback path is the norm)
Affected code
pkg/parser/remote_download_file.godownloadFileViaGitL321:exec.CommandContext(ctx, "git", "archive", "--remote="+repoURL, ref, path)downloadFileViaGitCloneL408:exec.CommandContext(ctx, "git", "-C", tmpDir, "checkout", ref)pkg/parser/remote_resolve_sha.goresolveRefToSHAViaGitL41 / L46:git ls-remote <url> <ref>andgit ls-remote <url> <prefix>+refAttacker path
owner/repo/path@<ref>. Theref(text after@) andpathare attacker-controlled via a workflow'simports:frontmatter — a third-party workflow that a victim compiles/installs (gh aw add,gh aw compile). Parsed byparseRemoteOrigin(pkg/parser/import_remote.go:41),parseWorkflowSpecParts(pkg/parser/remote_workflow_spec.go:110), andparseWorkflowSpec(pkg/cli/spec.go:332).owner/repoare validated (spec.go:374).refandpathget no leading-dash / character check.gitutil.IsAuthErroris true (noGH_TOKEN, "unauthorized", "forbidden", "permission denied", "saml enforcement" — seepkg/gitutil/gitutil.go:34), the code shells out togitwithref/pathin option-parsed positional slots and no--end-of-options separator (verified absent).git archive --remote=<url> <ref> <path>— aref/pathlike--output=/victim/pathmakes git-archive write its archive to an attacker-chosen file → arbitrary file write/overwrite.git ls-remote <url> <ref>— areflike--upload-pack=...is option-parsed → command/behavior control / DoS.Why it is credible after falsification
exec.CommandContext(no shell), so the existing#nosec G204note is right about shell injection but does not cover argument injection — arefstarting with-is an option, not a value.IsAuthErrortriggers on the very common token-less / unauthorized case, so the fallback is readily reachable (private repo, GHE host, or failing raw URL).ref; any non-SHA ref flows to git unchanged.pkg/workflow/name_validation.go:70(rejects leading-"to prevent argument injection"),pkg/workflow/npm_validation.go:93(npm view -- pkg),pkg/cli/update_command.go:384(--),pkg/workflow/pip_validation.go:66. The git fallbacks use none of these.Remediation
ref/pathbeginning with-at parse time (mirrorname_validation.go/pip_validation.go).--end-of-options on every git call taking a user-derived positional (git ls-remote <url> -- <ref>; for archive,--before<path>and validateref; avoid--output=-style values).git. Centralize a git-ref allowlist (no leading-, no.., no control chars) applied inparseWorkflowSpecParts,parseRemoteOrigin, andparseWorkflowSpec.Areas audited and found safe (no finding)
pkg/cli/spec.go:299);Authorization: Bearerattached only to an exact GitHub host allowlist (attachImportAuthHeader), and Go's default client strips auth on cross-host redirects. Theimports:path never accepts anhttp(s)://URL, so a third-party workflow cannot redirect fetches to an attacker host.gh auth token, never logged (only var names / redacted prefixes), and only sent to the GitHub host allowlist.secrets.govalues are used solely for MCP env injection.Scan scope: injection + navigation phases against the highest-risk entry points (remote import/download, ref resolution, token handling). Not an exhaustive whole-repo scan.