Skip to content

Harden scanner/relaunch exec paths against command and argument injection - #49063

Merged
pelikhan merged 14 commits into
mainfrom
copilot/sighthound-fix-security-findings
Jul 30, 2026
Merged

Harden scanner/relaunch exec paths against command and argument injection#49063
pelikhan merged 14 commits into
mainfrom
copilot/sighthound-fix-security-findings

Conversation

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Sighthound flagged critical CWE-78 findings across scanner and self-relaunch exec paths where Docker mounts/targets and relaunch arguments required stronger validation guarantees. This change adds explicit path/argument guards so all high-risk exec inputs are validated, normalized, and constrained before process launch.

  • Docker mount/path hardening (shared helpers)

    • Added pkg/cli/docker_args_validation.go to centralize safe Docker -v argument construction.
    • Enforces:
      • absolute validated host paths (fileutil.ValidateAbsolutePath)
      • absolute normalized container mount paths
      • rejection of control characters
      • regular-file requirement for readonly file mounts
  • Grant scanner input hardening

    • pkg/cli/grant.go now:
      • resolves Docker via fileutil.ResolveExecutablePath("docker")
      • validates readonly policy mount via shared helper
      • trims and validates imageRef (rejects whitespace/control chars, empty input)
  • Poutine and Runner Guard mount hardening

    • pkg/cli/poutine.go and pkg/cli/runner_guard.go now build mounts via shared validation helpers instead of string concatenation.
    • poutine also validates gitRoot through fileutil.ValidateAbsolutePath before mount construction.
  • Self-relaunch argument safety

    • pkg/cli/upgrade_command.go now rejects relaunch arguments containing NUL bytes before exec.Command(exe, newArgs...).
  • Focused unit coverage

    • Added/updated tests in:
      • pkg/cli/docker_args_validation_test.go
      • pkg/cli/grant_test.go
      • pkg/cli/upgrade_command_test.go
volumeMount, err := buildDockerReadonlyFileMount(policyFile, "/tmp/gh-aw-grant-policy.yaml")
if err != nil {
    return nil, fmt.Errorf("invalid grant policy mount: %w", err)
}

imageRef = strings.TrimSpace(imageRef)
if imageRef == "" || strings.ContainsAny(imageRef, " \t\r\n\x00") {
    return nil, fmt.Errorf("invalid grant image reference: %q", imageRef)
}

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 9.65 AIC · ⊞ 8.7K ·
Comment /souschef to run again

Copilot AI linked an issue Jul 30, 2026 that may be closed by this pull request
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix security findings in github/gh-aw Harden scanner/relaunch exec paths against command and argument injection Jul 30, 2026
Copilot AI requested a review from pelikhan July 30, 2026 06:34
@github-actions

Copy link
Copy Markdown
Contributor

Triage Summary

  • Category: bug
  • Risk: high
  • Priority Score: 74/100 (impact 42 + urgency 22 + quality 10)
  • Recommended Action: fast_track

Security-critical CWE-78 command/argument injection hardening for scanner/relaunch exec paths (Sighthound flagged). Still draft — needs undraft and expedited security review.

Generated by 🔧 PR Triage Agent · auto · 54.9 AIC · ⌖ 5.64 AIC · ⊞ 7.9K ·

@pelikhan
pelikhan marked this pull request as ready for review July 30, 2026 08:06
Copilot AI review requested due to automatic review settings July 30, 2026 08:06
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

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

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 Docker scanner mounts, Grant image inputs, and self-relaunch arguments before process execution.

Changes:

  • Adds shared Docker mount validation helpers.
  • Validates scanner paths, image references, and Docker executable resolution.
  • Adds focused validation tests.
Show a summary per file
File Description
pkg/cli/docker_args_validation.go Adds Docker mount validation helpers.
pkg/cli/docker_args_validation_test.go Tests mount validation.
pkg/cli/grant.go Validates Grant execution inputs.
pkg/cli/grant_test.go Tests invalid image references.
pkg/cli/poutine.go Validates and safely constructs mounts.
pkg/cli/runner_guard.go Uses the shared mount helper.
pkg/cli/upgrade_command.go Rejects NUL-containing relaunch arguments.
pkg/cli/upgrade_command_test.go Tests relaunch argument rejection.

Review details

Tip

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

  • Files reviewed: 8/8 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment thread pkg/cli/grant.go
Comment on lines +162 to +164
if strings.ContainsAny(imageRef, " \t\r\n\x00") {
return nil, fmt.Errorf("grant image reference contains invalid whitespace/control characters: %q", imageRef)
}
if err != nil {
return "", err
}
return cleanHostPath + ":" + cleanContainerPath, nil
@github-actions

Copy link
Copy Markdown
Contributor

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100 — Excellent

Analyzed 5 test(s): 5 design, 0 implementation, 0 violation(s).

Note: Pre-fetch diff stat files were empty (likely a pipeline artifact issue); test file contents were read directly via the GitHub MCP server for complete analysis.

📊 Metrics (5 tests)
Metric Value
Analyzed 5 (Go: 5, JS: 0)
✅ Design 5 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 5 (100%)
Duplicate clusters 0
Inflation No
🚨 Violations 0
Test File Classification Issues
TestBuildDockerVolumeMount pkg/cli/docker_args_validation_test.go design_test / behavioral_contract None
TestBuildDockerReadonlyFileMount pkg/cli/docker_args_validation_test.go design_test / behavioral_contract None
TestGrantRunOnImageRejectsInvalidImageRef pkg/cli/grant_test.go design_test / behavioral_contract None
TestRelaunchWithSameArgsRejectsRelativeExecutableOverride pkg/cli/upgrade_command_test.go design_test / behavioral_contract None
TestRelaunchWithSameArgsRejectsNullByteArgument pkg/cli/upgrade_command_test.go design_test / behavioral_contract None

Verdict

passed. 0% implementation tests (threshold: 30%). All 5 tests are injection/path-validation security guards with explicit error assertions, correct build tags (//go:build !integration), and no mock-library usage.

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 · 36.7 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: 100/100. 0% implementation tests (threshold: 30%). All 5 new tests are injection/path-validation security guards — design tests with explicit error assertions, correct build tags, and no mock-library usage.

@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.

Review: Harden scanner/relaunch exec paths

Good overall direction — the shared buildDockerReadonlyFileMount/buildDockerVolumeMount helpers, imageRef validation, and NUL-byte relaunch guard are all solid additions.

One blocking gap: poutine.go still passes bare "docker" to exec.Command in both runPoutineOnDirectory and runPoutineOnFile, while grant.go and runner_guard.go were both updated in this PR to use fileutil.ResolveExecutablePath("docker"). This is an inconsistency that leaves the stated hardening incomplete — see the inline comment for the fix.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 32.4 AIC · ⌖ 6.04 AIC · ⊞ 5.3K

Comment thread pkg/cli/poutine.go Outdated
return fmt.Errorf("invalid docker mount path: %w", err)
}
cmd := exec.Command(
"docker",

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.

poutine.go still uses bare "docker" here (and again at line 212), while grant.go and runner_guard.go were both updated in this PR to use fileutil.ResolveExecutablePath("docker").

Using a bare command name relies on PATH resolution at exec time. A malicious entry earlier in PATH could shadow the real Docker binary — the same weakness this PR explicitly hardens in the other two callers.

Fix both call sites (in runPoutineOnDirectory and runPoutineOnFile):

dockerPath, err := fileutil.ResolveExecutablePath("docker")
if err != nil {
    return fmt.Errorf("docker command not found: %w", err)
}
cmd := exec.Command(dockerPath, "run", ...)

@copilot please address this.

@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 three focused issues.

📋 Key Themes & Highlights

Key Themes

  • Container path colon gapvalidateContainerMountPath correctly rejects NUL/CR/LF but misses :, which Docker uses as a field separator in -v args. A colon in the container path would silently produce a malformed three-part mount string, exactly the class of injection this PR sets out to prevent.
  • Inconsistent docker executable resolutionpoutine.go still uses the hard-coded string "docker" in exec.Command while runner_guard.go and grant.go were upgraded to fileutil.ResolveExecutablePath. The hardening is incomplete for the poutine paths.
  • Fragile grant testTestGrantRunOnImageRejectsInvalidImageRef passes a hard-coded /tmp/policy.yaml that may not exist on the runner, and skips the empty-string and NUL-byte cases.

Positive Highlights

  • ✅ Clean centralisation of Docker mount construction into validated helpers.
  • buildDockerReadonlyFileMount enforces regular-file requirement and :ro suffix correctly.
  • ✅ NUL-byte guard in relaunchWithSameArgs is precise and well-tested.
  • imageRef TrimSpace + control-char rejection in grant.go follows defence-in-depth.

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

Comment thread pkg/cli/docker_args_validation.go Outdated
if containerPath == "" {
return "", errors.New("container path cannot be empty")
}
if strings.ContainsAny(containerPath, "\x00\r\n") {

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.

[/diagnosing-bugs] validateContainerMountPath does not reject colon (:) in the container path. A colon in the container-path component would split the assembled host:container string into unexpected Docker mount segments, undermining the injection protection this helper provides.

💡 Suggested fix

Add : to the rejected character set:

if strings.ContainsAny(containerPath, "\x00
:") {
    return "", errors.New("container path contains invalid control characters")
}

Without this, a path like /workdir:extra would produce hostPath:/workdir:extra, which Docker parses as three-part syntax (host:container:options).

@copilot please address this.

Comment thread pkg/cli/poutine.go Outdated
return fmt.Errorf("invalid docker mount path: %w", err)
}
cmd := exec.Command(
"docker",

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.

[/diagnosing-bugs] poutine.go calls exec.Command("docker", ...) (hard-coded string) while runner_guard.go and grant.go both resolve the path via fileutil.ResolveExecutablePath("docker"). This inconsistency means the poutine paths still rely on PATH lookup, which the PR explicitly hardened against in the other callers.

💡 Suggested fix

Resolve the docker executable before constructing the command, as done in runner_guard.go:

dockerPath, err := fileutil.ResolveExecutablePath("docker")
if err != nil {
    return fmt.Errorf("docker command not found: %w", err)
}
cmd := exec.Command(dockerPath, "run", ...)

Apply the same change in both runPoutineOnDirectory and runPoutineOnFile.

@copilot please address this.

require.NoError(t, err)
require.Equal(t, tmpDir+":/workdir", mount)

_, err = buildDockerVolumeMount("relative/path", "/workdir")

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.

[/tdd] The test for buildDockerVolumeMount does not cover a container path containing a colon or control characters, so the gap noted in the validateContainerMountPath check would not be caught by CI even after a fix.

💡 Suggested additional test cases
_, err = buildDockerVolumeMount(tmpDir, "/work:dir")
require.Error(t, err)

_, err = buildDockerVolumeMount(tmpDir, "/work\x00dir")
require.Error(t, err)

Similarly, TestBuildDockerReadonlyFileMount should cover an invalid container path (control chars / colon) to ensure the validation is exercised end-to-end through both helpers.

@copilot please address this.

Comment thread pkg/cli/grant_test.go
@@ -73,3 +74,13 @@ func TestGrantPolicyFile(t *testing.T) {
t.Fatalf("Expected policy file basename %q, got %q", grantPolicyFilename, filepath.Base(policyFile))
}
}

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.

[/tdd] TestGrantRunOnImageRejectsInvalidImageRef calls grantRunOnImage with a non-existent policy file path (/tmp/policy.yaml). The test may fail or pass for the wrong reason depending on whether that file exists on the runner, and it doesn't test the NUL-byte or empty-string cases either.

💡 Suggested approach

Create a temp policy file in the test and add separate sub-tests for each rejection condition:

func TestGrantRunOnImageRejectsInvalidImageRef(t *testing.T) {
    tmp := t.TempDir()
    policyFile := filepath.Join(tmp, "policy.yaml")
    require.NoError(t, os.WriteFile(policyFile, []byte("policy: true"), 0o644))

    _, err := grantRunOnImage("bad image", policyFile, false)
    require.ErrorContains(t, err, "invalid whitespace")

    _, err = grantRunOnImage("", policyFile, false)
    require.ErrorContains(t, err, "cannot be empty")

    _, err = grantRunOnImage("img\x00ref", policyFile, false)
    require.ErrorContains(t, err, "invalid whitespace")
}

@copilot please address this.

@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 with one non-blocking hardening gap

Solid, narrowly-scoped security hardening PR with good test coverage for the new validation helpers. One inconsistency found (non-blocking) — see inline comment.

Details
  • New docker_args_validation.go helpers (buildDockerVolumeMount, buildDockerReadonlyFileMount, validateContainerMountPath) correctly reject non-absolute paths, control characters, and non-regular files, and are exercised by focused unit tests.
  • grant.go and runner_guard.go correctly resolve docker via fileutil.ResolveExecutablePath before exec.Command, matching the PR's stated goal.
  • poutine.go's two call sites (runPoutineOnDirectory, runPoutineOnFile) still hardcode the "docker" literal in exec.Command instead of using the same ResolveExecutablePath resolution used elsewhere in this PR — an inconsistency in an otherwise-thorough hardening pass (flagged inline).
  • upgrade_command.go's NUL-byte guard on relaunch args is reasonable defense-in-depth, well tested.
  • Volume mount construction (string concatenation replaced by validated helper) is a genuine improvement over the prior direct string interpolation.

This is not blocking merge since the flagged inconsistency doesn't introduce a new vulnerability (docker is still resolved via the OS's normal PATH lookup semantics through exec.Command, just without the extra ValidateExecutablePath step); it is a consistency/completeness gap worth addressing to match the PR's own security posture.

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

Comment thread pkg/cli/poutine.go Outdated
return fmt.Errorf("invalid docker mount path: %w", err)
}
cmd := exec.Command(
"docker",

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.

Docker path here is still resolved implicitly via PATH (exec.Command("docker", ...)) while grant.go and runner_guard.go in this same PR now use fileutil.ResolveExecutablePath("docker") for equivalent hardening.

💡 Why this matters

The PR's stated purpose is to harden all Docker exec paths against command/argument injection by validating executable resolution (ResolveExecutablePath calls exec.LookPath then ValidateExecutablePath). runPoutineOnDirectory and runPoutineOnFile (line 212) skip this and rely on bare exec.Command("docker", ...), leaving two of the four hardened call sites without the same PATH-resolution validation the PR introduces elsewhere. This is an inconsistent security posture within a single commit whose whole purpose is exec-path hardening — if ValidateExecutablePath catches something meaningful (e.g. a symlink or non-regular-file executable), poutine silently bypasses that check.

dockerPath, err := fileutil.ResolveExecutablePath("docker")
if err != nil {
    return fmt.Errorf("docker command not found: %w", err)
}
cmd := exec.Command(
    dockerPath,
    "run",
    ...
)

Apply the same pattern at line 212 as well.

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix https://github.com/github/gh-aw/actions/runs/30529768495/job/90836955382?pr=49063

Addressed in 26b202e. I updated the new validation errors in the hardened Docker/Grant/poutine/runner-guard/relaunch paths to include concrete usage examples so the lint-error-messages job stays above threshold, then reran the local checks.

@pelikhan

Copy link
Copy Markdown
Collaborator

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix tests https://github.com/github/gh-aw/actions/runs/30533441387/job/90843565969?pr=49063

Addressed in a8eed6d. I synced pkg/cli/data/agentic_workflows_fallback_aw_files.json with the current .github/aw/*.md contents so the failing TestFallbackAWFilesMatchesLocalAWDirectory check passes again; targeted go test ./pkg/cli -run TestFallbackAWFilesMatchesLocalAWDirectory -count=1 and make agent-report-progress both passed.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

@copilot please run the pr-finisher skill, address the failing checks below, and rerun the necessary tests once the branch is up to date.

Failed checks:

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 5.6 AIC · ⊞ 8.7K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

[!WARNING]
threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding....

Addressed in 3d0dc4b. I updated the cache-based CLI tests to seed the complete-download marker expected by the current artifact downloader and made scripts/sync-compat.sh portable on macOS/BSD grep by replacing grep -P with sed. Local make build, targeted repro tests, make lint, make test, make test-unit, and make agent-report-progress passed. The GitHub checks on this new head are still stale because agent pushes do not re-trigger CI; a maintainer will need to re-run them to verify green on 3d0dc4b.

@github-actions

Copy link
Copy Markdown
Contributor

PR Triage

Rationale: Addresses CWE-78 command/argument injection findings in scanner and self-relaunch exec paths — security-critical. Adds new validation helpers, tests, and an ADR. Currently has a merge conflict (dirty) blocking merge; reviews mixed (approved then changes requested, dismissed). Given security severity, recommend expedited maintainer attention once rebased.

Generated by 🔧 PR Triage Agent · auto · 69.5 AIC · ⌖ 8.05 AIC · ⊞ 7.9K ·

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

Warning

threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding.

Details

The threat detection engine failed to produce results.

Review the workflow run logs for details.

@copilot please run the pr-finisher skill address any unresolved review feedback and rerun checks once updates are pushed.

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 9.65 AIC · ⊞ 8.7K ·
Comment /souschef to run again

Copilot AI commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

[!WARNING]
threat detection engine error
The threat detection engine encountered an error and could not complete analysis. This is a tooling failure, not a security finding....

Current HEAD e924ffa already contains the Docker/grant/poutine/test hardening requested in the remaining review threads, and I revalidated the related regressions locally (TestBuildDockerVolumeMount, TestBuildDockerReadonlyFileMount, TestGrantRunOnImageRejectsInvalidImageRef, TestGrantRunOnImageVerboseCommandEscapesImageRef, TestViewWorkflowRun_*, TestAuditCachingBehavior). The latest PR checks on e924ffa are already in progress; this agent cannot re-trigger CI if they go stale.

@pelikhan
pelikhan merged commit 727dc45 into main Jul 30, 2026
29 checks passed
@pelikhan
pelikhan deleted the copilot/sighthound-fix-security-findings branch July 30, 2026 13:19
Copilot stopped work on behalf of gh-aw-bot due to an error July 30, 2026 13:19
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.84.1

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.

[sighthound] Security findings in github/gh-aw

4 participants