Skip to content

Harden exec.Command inputs for scanner Docker invocations and upgrade re-exec - #49996

Merged
pelikhan merged 7 commits into
mainfrom
copilot/sighthound-security-findings
Aug 3, 2026
Merged

Harden exec.Command inputs for scanner Docker invocations and upgrade re-exec#49996
pelikhan merged 7 commits into
mainfrom
copilot/sighthound-security-findings

Conversation

Copilot AI commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Sighthound flagged high-confidence command-injection risk paths where dynamic values flow into exec.Command arguments (scanner docker args and upgrade relaunch args). This PR tightens validation/allow-listing at argument construction points to ensure only expected path/image/flag shapes are executable.

  • Docker image reference hardening (pkg/cli/docker_args_validation.go)

    • Enforced structured validation in validateDockerImageRef:
      • reject control/whitespace characters and leading -
      • allow at most one @ digest separator
      • require valid digest shape (algo:hex{32,})
      • validate tag format ([A-Za-z0-9_][A-Za-z0-9_.-]{0,127})
      • validate image/repository name against an allow-listed pattern
    • Keeps existing non-shell invocation model and narrows accepted dynamic values.
  • Upgrade relaunch argument hardening (pkg/cli/upgrade_command.go)

    • relaunchWithSameArgs now allow-lists extraFlag to known internal flags only:
      • --skip-extension-upgrade
      • --post-upgrade
    • Added argument hygiene checks:
      • reject empty args
      • reject control/format characters in forwarded args
  • Focused regression coverage

    • Extended tests in:
      • pkg/cli/docker_args_validation_test.go
      • pkg/cli/upgrade_command_test.go
    • Added reject/accept cases for digest/tag/name parsing and unknown relaunch flags.
// upgrade_command.go
allowedExtraFlags := map[string]struct{}{
    "--skip-extension-upgrade": {},
    "--post-upgrade":           {},
}
if _, ok := allowedExtraFlags[extraFlag]; !ok {
    return fmt.Errorf("invalid relaunch flag %q", extraFlag)
}

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 20.5 AIC · ⌖ 9.4 AIC · ⊞ 5.9K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 15.3 AIC · ⌖ 9 AIC · ⊞ 8.3K ·
Comment /souschef to run again

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Thanks for working on addressing the security findings from the Sighthound scan! This PR properly follows the agentic workflow process — assigned to a core team member, addressing a valid security issue (#49911), and marked as WIP during development. The approach of collecting remediation steps (path validation, sanitization, allow-listing) across the five critical locations aligns well with the guidance provided. Once the implementation is underway, reviewers will have a clear security focus to evaluate. Keep the PR description updated as you make progress! 🔒

Generated by ✅ Contribution Check · auto · 45.9 AIC · ⌖ 3.93 AIC · ⊞ 8.8K ·

… sites

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix security vulnerabilities in github/gh-aw Harden exec.Command inputs for scanner Docker invocations and upgrade re-exec Aug 3, 2026
Copilot AI requested a review from pelikhan August 3, 2026 14:00
@pelikhan
pelikhan marked this pull request as ready for review August 3, 2026 14:41
Copilot AI review requested due to automatic review settings August 3, 2026 14:41
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

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

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (76 additions detected).

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 and upgrade relaunch arguments against unsafe input.

Changes:

  • Adds structured Docker image-reference validation.
  • Restricts relaunch flags and rejects unsafe forwarded arguments.
  • Adds focused validation tests.
Show a summary per file
File Description
pkg/cli/docker_args_validation.go Validates image names, tags, and digests.
pkg/cli/docker_args_validation_test.go Tests accepted and rejected image references.
pkg/cli/upgrade_command.go Hardens upgrade relaunch arguments.
pkg/cli/upgrade_command_test.go Tests relaunch validation failures.

Review details

Tip

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

  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread pkg/cli/docker_args_validation.go Outdated
Comment thread pkg/cli/upgrade_command.go Outdated
Comment on lines 490 to 495
if arg == "" {
return errors.New("invalid relaunch arguments: argument cannot be empty. Example: compile .github/workflows/example.md")
}
if containsControlCharacters(arg) {
return errors.New("invalid relaunch arguments: argument contains invalid control characters. Example: compile .github/workflows/example.md")
}
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 87/100 — Excellent

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

📊 Metrics (9 scenarios across 2 test files)
Metric Value
Analyzed 9 (Go: 9, JS: 0)
✅ Design 9 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 5 (56%)
Duplicate clusters 0
Inflation No
🚨 Violations 0
Test File Classification Issues
TestValidateDockerImageRefRejectsUnsafeCharacters (+4 rows) docker_args_validation_test.go:88 behavioral_contract, high_value, design_test None
TestValidateDockerImageRefAcceptsCommonReferences (4 subtests) docker_args_validation_test.go:102 behavioral_contract, high_value, design_test Happy-path only — acceptable as allowlist complement
TestRelaunchWithSameArgsRejectsUnknownExtraFlag upgrade_command_test.go:160 behavioral_contract, high_value, design_test None
TestRelaunchWithSameArgsRejectsNullByteArgument (msg update) upgrade_command_test.go:153 behavioral_contract, high_value, design_test None

Verdict

passed. 0% implementation tests (threshold: 30%). All new tests verify security/validation design invariants with comprehensive error-path and allowlist coverage. No forbidden mocks, no missing build tags.

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 · 34.1 AIC · ⌖ 10.2 AIC · ⊞ 8.5K ·
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: 87/100. 0% implementation tests (threshold: 30%).

@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 correctness issues and one test hygiene issue.

📋 Key Themes & Highlights

Issues

  1. Digest minimum length too permissive (docker_args_validation.go:21) — allows 32-char (MD5-length) digests; OCI production minimum is 64 hex chars (SHA-256). Tighten {32,}{64,}.
  2. Registry hostname case inconsistency (docker_args_validation.go:19) — the hostname regex allows uppercase while OCI spec normalises to lowercase, creating a subtle allow/deny mismatch.
  3. Stale test name (upgrade_command_test.go:149) — TestRelaunchWithSameArgsRejectsNullByteArgument describes NUL bytes but the check now covers all control characters; rename to match the new behaviour.

Positive Highlights

  • ✅ Excellent allow-list approach for relaunchWithSameArgs — enumerated set is simple and auditable
  • ✅ Structured digest/tag/name parsing (cut + regex) is robust and easy to test
  • ✅ Good separation between reject tests (TestValidateDockerImageRefRejectsUnsafeCharacters) and accept tests (TestValidateDockerImageRefAcceptsCommonReferences)
  • ✅ Upgrading from NUL-only to full control-character check is a meaningful improvement

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 50.6 AIC · ⌖ 8.91 AIC · ⊞ 7.1K
Comment /matt to run again

Comments that could not be inline-anchored

pkg/cli/docker_args_validation.go:21

[/diagnosing-bugs] dockerImageDigestRef accepts any hex string ≥ 32 chars, which admits MD5-length digests — weaker than OCI/Docker actually requires.

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

Tighten the minimum to 64 hex chars (SHA-256 is the production baseline):

dockerImageDigestRef = regexp.MustCompile(`^\[A-Za-z\]\[A-Za-z0-9\]*:\[0-9a-fA-F\]{64,}$`)

MD5 (32) and SHA-1 (40) are not used by OCI registries for content addressing; allowing them widens the attack surfac…

pkg/cli/docker_args_validation.go:19

[/diagnosing-bugs] dockerImageNamePattern allows uppercase letters in the registry hostname (e.g. GHCR.IO/...) but Docker normalises registry hostnames to lowercase — this inconsistency could let a case-variant hostname bypass or be rejected unexpectedly.

<details>
<summary>💡 Details</summary>

The pattern currently is:

^(?:[a-zA-Z0-9.-]+(?::[0-9]+)?/)?[a-z0-9]+...

The hostname portion ([a-zA-Z0-9.-]+) permits uppercase while OCI spec and Docker normalise to lowercase. Cons…

pkg/cli/upgrade_command_test.go:149

[/tdd] The test name TestRelaunchWithSameArgsRejectsNullByteArgument no longer matches the implementation — NUL bytes are now rejected as control characters, not specifically as NUL bytes. The name is misleading for future readers.

<details>
<summary>💡 Suggested rename</summary>

func TestRelaunchWithSameArgsRejectsControlCharacterInArgument(t *testing.T) {

Clear test names read as specifications; stale names erode trust in the test suite.

</details>

@copilot please address…

pkg/cli/docker_args_validation_test.go:121

[/tdd] The accept-case tests don't cover an image with both a tag and a digest (image:tag@sha256:...) — this is a valid OCI reference form and worth an explicit positive test to confirm the parser handles it.

<details>
<summary>💡 Suggested addition</summary>

&quot;ghcr.io/org/image:1.0@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&quot;,

Without this case it's easy for a refactor to silently break tag+digest references.

</details>

@copilot please address th…

@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: Request Changes

The hardening direction is sound, but one change introduces a functional regression, and the digest validation is looser than intended.

💡 Themes
  • Regression: the new empty-string check in relaunchWithSameArgs applies to all forwarded args (os.Args[1:]), not just the appended flag, which can break legitimate re-exec with empty-string arguments. This isn't a security fix (exec.Command never shells out) and should be scoped or removed.
  • Weak validation: dockerImageDigestRef accepts any alphanumeric "algorithm" prefix with 32+ hex chars instead of the real digest algorithm set (sha256/sha512) with exact lengths, undermining the stated goal of tightening the allow-list.
  • Image name/tag parsing logic for host:port-without-tag refs (e.g. localhost:5000/image) was manually verified to work correctly, no issue there.
  • Test coverage for the new regex paths is reasonable but could add explicit host:port-without-tag and uppercase-repo-name cases.

🔎 Code quality review by PR Code Quality Reviewer · auto · 47.8 AIC · ⌖ 8.1 AIC · ⊞ 7.9K
Comment /review to run again

Comment thread pkg/cli/upgrade_command.go Outdated
for _, arg := range newArgs {
if strings.ContainsRune(arg, '\x00') {
return errors.New("invalid relaunch arguments: argument contains NUL byte. Example: compile .github/workflows/example.md")
if arg == "" {

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.

Empty-string argument check now applies to every forwarded arg (os.Args[1:]), not just the appended extraFlag, which can turn legitimate empty-string CLI arguments into a hard re-exec failure.

💡 Details

Before this PR, the loop only rejected NUL bytes in newArgs. Now arg == "" unconditionally errors for any element of os.Args[1:], including args the user actually passed (e.g. an explicit empty-string flag value, or an empty positional arg from shell quoting like gh-aw compile ""). This is a functional regression introduced under the guise of hardening: an empty string is not a command-injection vector for exec.Command (which never invokes a shell), so rejecting it adds no security value but breaks previously-working invocations during re-exec after upgrade.

Suggested fix: drop the empty-string check entirely, or scope it to only extraFlag if empty extra flags are truly invalid.

Comment thread pkg/cli/docker_args_validation.go Outdated
var (
dockerImageNamePattern = regexp.MustCompile(`^(?:[a-zA-Z0-9.-]+(?::[0-9]+)?/)?[a-z0-9]+(?:[._-][a-z0-9]+)*(?:/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$`)
dockerImageTagPattern = regexp.MustCompile(`^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$`)
dockerImageDigestRef = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9]*:[0-9a-fA-F]{32,}$`)

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 digest pattern accepts any 32+ hex-char string after any alphanumeric "algorithm" name, which is far looser than real digest algorithms and provides only superficial validation.

💡 Details

dockerImageDigestRef = ^[A-Za-z][A-Za-z0-9]*:[0-9a-fA-F]{32,}$ will accept things like x:00000000000000000000000000000000 as a "valid" digest — there's no allow-list of known algorithms (sha256, sha512) nor exact-length enforcement (sha256 digests are always exactly 64 hex chars). Since this validation is explicitly meant to be a security allow-list (per the PR description), it should assert the real digest shape precisely rather than a loose superset, otherwise malformed/attacker-controlled digest-shaped strings slip through unnoticed.

Suggested fix: ^sha256:[0-9a-fA-F]{64}$|^sha512:[0-9a-fA-F]{128}$ (or whatever algorithm set is actually supported downstream).

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

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 51.4 AIC · ⌖ 7.45 AIC · ⊞ 5.4K

Comment thread pkg/cli/upgrade_command.go Outdated
func relaunchWithSameArgs(extraFlag string, exeOverride string) error {
allowedExtraFlags := map[string]struct{}{
"--skip-extension-upgrade": {},
"--post-upgrade": {},

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.

Dead allowlist entry: --post-upgrade is in the allowlist but has no call sites — only --skip-extension-upgrade is ever passed as extraFlag. If this is reserved for future use, add a comment. Otherwise remove it to keep the security surface minimal.

@copilot please address this.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot sous-chef triage:

This PR still has unresolved review feedback covering both correctness and compatibility.

Generated by PR Sous Chef · run: https://github.com/github/gh-aw/actions/runs/30838483274

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 20.5 AIC · ⌖ 9.4 AIC · ⊞ 5.9K ·
Comment /souschef to run again

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

Copilot AI commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@copilot sous-chef triage:

This PR still has unresolved review feedback covering both correctness and compatibility....

Addressed in 223a896.

  • Restored valid Docker repo separators (__, repeated hyphens) and tightened digest validation to exact supported shapes (sha256/sha512).
  • Stopped rejecting empty forwarded argv entries during re-exec.
  • Removed the unused --post-upgrade relaunch allowlist entry.
  • Local validation passed with make agent-report-progress; targeted CLI tests passed.
  • CI on this new head is still stale/unverified until a maintainer re-triggers it.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

PR Triage: #49996

  • Category: bug
  • Risk: high
  • Score: 72/100 (Impact 40 + Urgency 20 + Quality 12)
  • Recommended action: fast_track

Hardens exec.Command inputs against command-injection risk in scanner Docker invocations and upgrade re-exec. Security-relevant; no CI runs recorded yet — verify before merge.

Generated by 🔧 PR Triage Agent · auto · 51.1 AIC · ⌖ 4.08 AIC · ⊞ 8K ·

@pelikhan
pelikhan merged commit bc6a04d into main Aug 3, 2026
29 checks passed
@pelikhan
pelikhan deleted the copilot/sighthound-security-findings branch August 3, 2026 19:14
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot This PR still has unresolved blocking review feedback. Please address these threads, then run the pr-finisher skill and summarize merge readiness.

Unresolved reviews:

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 15.3 AIC · ⌖ 9 AIC · ⊞ 8.3K ·
Comment /souschef to run again

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.84.4

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