Skip to content

fix: argument injection in git archive fallback (CWE-88) - #49500

Merged
pelikhan merged 4 commits into
mainfrom
copilot/fix-argument-injection-vulnerability
Aug 1, 2026
Merged

fix: argument injection in git archive fallback (CWE-88)#49500
pelikhan merged 4 commits into
mainfrom
copilot/fix-argument-injection-vulnerability

Conversation

CopilotAI commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

The downloadWorkflowContentViaGit in pkg/cli/download_workflow.go passed path directly to git archive without validation or an end-of-options separator. An attacker-controlled source:/redirect: frontmatter value like --output=/home/dev/.ssh/authorized_keys would be parsed by git as a flag, enabling arbitrary local file writes via the auth-error fallback path.

Changes

  • pkg/cli/download_workflow.go: Mirror the hardening already applied in pkg/parser/remote_download_file.go:
    • Call gitutil.ValidateGitRef(ref) and gitutil.ValidateGitPath(path) before constructing the command, rejecting values that start with -, contain .., or are empty
    • Insert "--" end-of-options separator before path
// Beforecmd:=exec.CommandContext(ctx, "git", "archive", "--remote="+repoURL, ref, path)
// Afteriferr:=gitutil.ValidateGitRef(ref); err!=nil {
returnnil, fmt.Errorf("refusing git fallback: %w", err)
}
iferr:=gitutil.ValidateGitPath(path); err!=nil {
returnnil, fmt.Errorf("refusing git fallback: %w", err)
}
cmd:=exec.CommandContext(ctx, "git", "archive", "--remote="+repoURL, ref, "--", path)
  • pkg/cli/download_workflow_test.go: Add TestDownloadWorkflowContentViaGitValidation covering path injection (--output=), ref injection, path traversal (../), .. in ref, empty ref, and empty path — all verified to return early with "refusing git fallback" before any subprocess is spawned.

CopilotAIand others added 2 commits August 1, 2026 06:58
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
CopilotAI changed the title [WIP] Fix argument injection vulnerability in git archive fallbackfix: argument injection in git archive fallback (CWE-88)Aug 1, 2026
CopilotAI requested a review from pelikhanAugust 1, 2026 07:06
@github-actions

Copy link
Copy Markdown
Contributor

PR Triage

  • Category:bug | Risk:high | Priority:low
  • Score: 32/100 (impact 15 + urgency 15 + quality 2)
  • Recommended action:defer
  • Batch:wip-drafts

Note: Draft, security-relevant (argument injection) but no diff yet - monitor closely once ready

Generated by 🔧 PR Triage Agent · auto · 48.9 AIC · ⌖ 6.24 AIC · ⊞ 7.9K ·

@pelikhan
pelikhan marked this pull request as ready for review August 1, 2026 07:20
CopilotAI review requested due to automatic review settings August 1, 2026 07:20
@github-actions

github-actionsBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #49500 does not have the 'implementation' label and has only 78 new lines of code in business logic directories (threshold: 100).

@github-actions

github-actionsBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actionsBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actionsBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens the Git archive fallback against CWE-88 argument injection, addressing #49498.

Changes:

  • Validates Git refs and paths and adds an end-of-options separator.
  • Adds regression tests for malicious inputs.
  • Includes an unrelated generated workflow update requiring removal or explanation.
Show a summary per file
FileDescription
pkg/cli/download_workflow.goSecures Git archive arguments.
pkg/cli/download_workflow_test.goTests rejected refs and paths.
.github/workflows/daily-byok-ollama-test.lock.ymlUpdates unrelated threat-detection configuration.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Balanced

(umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}"
printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.43/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"targets\":{\"copilot\":{\"host\":\"host.docker.internal:11434\"}}},\"container\":{\"imageTag\":\"0.27.43,squid=sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d,agent=sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6,api-proxy=sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1,cli-proxy=sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.43/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"defaultAiCreditsPricing\":{\"input\":0.000001,\"output\":0.000001},\"targets\":{\"copilot\":{\"host\":\"host.docker.internal:11434\"}},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.43,squid=sha256:26be5e0b8c8f4c41c8a59126b29bb5d80b07253597472ded2a16bdd75abcbf9d,agent=sha256:04e2d1987a565000a8f114b89d806ae7a3864dd4f944be65275b28c93d8690e6,api-proxy=sha256:d85f57975af5ea23af4996e41ed73fbc8f5b4a47402472bfe82e508f352cb0c1,cli-proxy=sha256:65c45ea2967984d0024f3df61bc71335658a77ede96c8d9665da7a5f33a795ab\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix correctly addresses CWE-88 argument injection in downloadWorkflowContentViaGit. ValidateGitRef and ValidateGitPath reject empty values, leading dash, NUL bytes, and dotdot traversal before any subprocess is spawned. The -- end-of-options separator adds defence-in-depth. Tests cover all six injection/traversal cases. LGTM.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 25.5 AIC · ⌖ 11.8 AIC · ⊞ 5.3K

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — requesting changes on two small but important gaps.

📋 Key Themes & Highlights

Key Themes

  • Clone fallback not fully hardened: downloadWorkflowContentViaGitClone accepts ref without validation and passes it to git checkout without a -- separator. It is only reachable through the validated archive path today, but standalone callers would be vulnerable.
  • #nosec comment inaccuracy: The suppression comment says ref/path are developer-authored, but the threat model in the PR description correctly identifies them as attacker-controlled. The mismatch undermines the value of future security audits.
  • Missing happy-path test: All 6 test cases assert rejection; none assert that valid inputs actually pass validation and proceed (even to a subprocess failure). This leaves a logic inversion undetected.

Positive Highlights

  • ✅ Core fix is correct and minimal — mirrors the pattern from pkg/parser/remote_download_file.go exactly
  • -- end-of-options separator is defence-in-depth even after validation — correct approach
  • ✅ Test names are clear specifications; table-driven structure is clean
  • gitutil.ValidateGitRef / ValidateGitPath reuse existing, tested helpers rather than reinventing validation

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 38.9 AIC · ⌖ 11.8 AIC · ⊞ 7K
Comment /matt to run again

Comments that could not be inline-anchored

pkg/cli/download_workflow.go:150

[/diagnosing-bugs]downloadWorkflowContentViaGitClone passes ref to git checkout without a -- end-of-options separator. Currently reachable only via downloadWorkflowContentViaGit (which validates first), but if ever called directly a leading-dash ref could be parsed as a git flag, bypassing the fix.

<details>
<summary>💡 Suggested fix</summary>

Add ValidateGitRef(ref) at the top of downloadWorkflowContentViaGitClone and use -- in checkout:

// top of downloadWorkflowC…</details><details><summary>pkg/cli/download_workflow.go:47</summary>**[/diagnosing-bugs]**The`#nosec G204`commentsaysref/pathare&quot;authoredbythedeveloper&quot;, butthePR&#39;sownthreatmodelshowstheycomefromattacker-controlled`source:`/`redirect:` frontmatter. Thecommentshouldbeupdatedtoaccuratelyreflectthetrustboundary.
&lt;details&gt;
&lt;summary&gt;💡 Suggestedupdate&lt;/summary&gt;
```go// #nosec G204 -- repoURL is derived from the developer&#39;s workflow import configuration.// ref and path are user-supplied values validated above by ValidateGitRef/Vali…</details><details><summary>pkg/cli/download_workflow_test.go:140</summary>**[/tdd]** The test suite covers all 6 rejection cases but has no happy-path test. Without one, a future refactor that breaks the early-return logic (e.g. validation always returns an error) would go undetected.&lt;details&gt;&lt;summary&gt;💡 Suggested addition&lt;/summary&gt;Add a table entry with a valid repo/ref/path that should *not* error on validation. Since the subprocess will fail in a test environment, assert the error does **not** contain `&quot;refusinggitfallback&quot;`:```go
{
name: &quot;valid input…
</details>

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 85/100 — Excellent

Analyzed 1 test function with 6 subtests: 1 design test, 0 implementation tests, 0 violations.

📊 Metrics (1 test, 6 subtests)
MetricValue
Analyzed1 behavioral test (6 table-driven rows)
✅ Design1 (100%)
⚠️ Implementation0 (0%)
Edge/error coverage6/6 (100%) — all rows verify error handling
Duplicate clusters0
Inflation4.6:1 (65 test lines : 14 prod lines)
🚨 Violations0

Test Details:

TestFileClassificationCoverage
TestDownloadWorkflowContentViaGitValidationpkg/cli/download_workflow_test.go:80–142Design test (security)6 edge cases (CWE-88 injection vectors)
✅ Test Quality Strengths
  • Security-focused design test: All 6 subtests verify argument injection (CWE-88) boundary conditions before git subprocess invocation
  • Comprehensive attack vector coverage: path dash-prefix, ref dash-prefix, path traversal (../), ref dotdot (..), empty ref, empty path
  • Proper error handling: Both require.Error() (mandatory error) and assert.Contains() (specific error message) assertions present
  • Table-driven structure: Each scenario is independent and clearly named; easy to add new cases
  • No mock libraries: Direct function testing (appropriate for unit test)
  • Build tag compliance: //go:build !integration present on line 1
  • Justified inflation: 4.6:1 ratio reflects comprehensive security validation with table-driven rows; each row is necessary

Verdict

Passed. 0% implementation tests (threshold: 30%). 1 design test covering 6 security-critical edge cases with proper error assertions. No violations.

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 19.3 AIC · ⌖ 7.15 AIC · ⊞ 8.4K ·
Comment /review to run again

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 85/100 — Excellent. 1 design test with 6 security-critical edge cases (CWE-88 argument injection), 0% implementation tests (threshold: 30%), no violations.

@github-actionsgithub-actionsBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: Approve

Clean, well-tested CWE-88 fix. Reuses existing gitutil.ValidateGitRef/ValidateGitPath validators consistently with pkg/parser/remote_download_file.go, adds the -- end-of-options separator as defence-in-depth, and includes solid negative-path test coverage (dash-prefixed ref/path, traversal, empty values) verifying the subprocess is never spawned.

💡 Notes

No blocking issues found in the diff. The lock.yml change appears to be an unrelated auto-generated regeneration artifact bundled into the PR — worth double-checking it wasn't accidentally included, but it's not a security or correctness concern for this fix.

🔎 Code quality review by PR Code Quality Reviewer · auto · 17.2 AIC · ⌖ 11.2 AIC · ⊞ 7.8K
Comment /review to run again

@github-actionsgithub-actionsBot mentioned this pull request Aug 1, 2026
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

CopilotAI commented Aug 1, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot run pr-finisher skill

Investigated the lock file diff. The .github/workflows/daily-byok-ollama-test.lock.yml change is the correct compiler-generated output from the unchanged .md source — the compiler itself was updated (adding defaultAiCreditsPricing and the models routing table to the firewall config), so make recompile now produces a different lock file from the same markdown source. Reverting to the old lock file causes a drift error in the pre-commit gate (make check-workflow-drift). The current state is in sync and all pre-PR checks pass.

@pelikhan
pelikhan merged commit 85d757b into mainAug 1, 2026
8 checks passed
@pelikhan
pelikhan deleted the copilot/fix-argument-injection-vulnerability branch August 1, 2026 07:59
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.84.2

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[vulnhunter] VulnHunter findings in github/gh-aw

3 participants

@pelikhan