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

Copilot AI 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
// Before
cmd := exec.CommandContext(ctx, "git", "archive", "--remote="+repoURL, ref, path)

// After
if err := gitutil.ValidateGitRef(ref); err != nil {
    return nil, fmt.Errorf("refusing git fallback: %w", err)
}
if err := gitutil.ValidateGitPath(path); err != nil {
    return nil, 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.

Copilot AI and 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>
Copilot AI changed the title [WIP] Fix argument injection vulnerability in git archive fallback fix: argument injection in git archive fallback (CWE-88) Aug 1, 2026
Copilot AI requested a review from pelikhan August 1, 2026 07:06
@github-actions

github-actions Bot commented Aug 1, 2026

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
Copilot AI review requested due to automatic review settings August 1, 2026 07:20
@github-actions

github-actions Bot 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-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

Copilot AI 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
File Description
pkg/cli/download_workflow.go Secures Git archive arguments.
pkg/cli/download_workflow_test.go Tests rejected refs and paths.
.github/workflows/daily-byok-ollama-test.lock.yml Updates 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-actions github-actions Bot 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-actions github-actions Bot 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` comment says ref/path are &quot;authored by the developer&quot;, but the PR&#39;s own threat model shows they come from attacker-controlled `source:`/`redirect:` frontmatter. The comment should be updated to accurately reflect the trust boundary.

&lt;details&gt;
&lt;summary&gt;💡 Suggested update&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;refusing git fallback&quot;`:

```go
{
    name: &quot;valid input…

</details>

@github-actions

github-actions Bot commented Aug 1, 2026

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)
Metric Value
Analyzed 1 behavioral test (6 table-driven rows)
✅ Design 1 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 6/6 (100%) — all rows verify error handling
Duplicate clusters 0
Inflation 4.6:1 (65 test lines : 14 prod lines)
🚨 Violations 0

Test Details:

Test File Classification Coverage
TestDownloadWorkflowContentViaGitValidation pkg/cli/download_workflow_test.go:80–142 Design 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-actions github-actions Bot 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-actions github-actions Bot 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-actions github-actions Bot mentioned this pull request Aug 1, 2026
@pelikhan

pelikhan commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Copilot AI commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

@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 main Aug 1, 2026
8 checks passed
@pelikhan
pelikhan deleted the copilot/fix-argument-injection-vulnerability branch August 1, 2026 07:59
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.84.2

Sign up for free to 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