Skip to content

Validate uv package names against PEP 508 before uv pip show - #51016

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

Validate uv package names against PEP 508 before uv pip show#51016
pelikhan merged 7 commits into
mainfrom
copilot/sighthound-security-findings-again

Conversation

Copilot AI commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Sighthound flagged five Critical command-injection findings around exec.Command calls built from dynamic data. Four of the five are already mitigated in the current code; the exception is the uv validation path, which only rejected --prefixed names.

Audit of reported findings

# Location Status
2 pip_validation.gopip index versions <pkg> --pre Already guarded by validatePipPackageName (PEP 508 allowlist)
3 dependabot.gonpm install --package-lock-only Fixed argument slice; npmPath resolved via fileutil.ResolveExecutablePath
4/5 poutine.godocker run -v <volumeMount> buildDockerVolumeMount canonicalizes and validates host path (absolute, no :) and container path
1 pip_validation.gouv pip show <pkg> --no-cache Gap — addressed here

Changes

  • pkg/workflow/pip_validation.go: validateUvPackages now applies validatePipPackageName to each extracted package name before the exec.Command call, matching the pip path. Invalid names are collected into the existing errors slice and surfaced as a validation error rather than reaching uv. Added a #nosec G204 justification consistent with the pip call site.
  • pkg/workflow/argument_injection_test.go: regression test asserting a malformed name is rejected.

Package extraction is permissive enough to yield names like pkg;whoami from uvx pkg;whoami in workflow custom steps, which previously flowed straight into the argv:

// before: only a '-' prefix check upstream
cmd := exec.Command(uvPath, "pip", "show", pkgName, "--no-cache")

// after
if err := validatePipPackageName(pkgName); err != nil {
    errors = append(errors, fmt.Sprintf("uv package '%s' is invalid: %v", pkg, err))
    continue
}
cmd := exec.Command(uvPath, "pip", "show", pkgName, "--no-cache")

Note on the test guard: it skips when uv is absent, because validateUvPackages then falls back to the pip path where invalid names are warnings rather than errors. Worth a second opinion on whether that fallback should also hard-fail.


Run context: https://github.com/github/gh-aw/actions/runs/31185909757> Generated by 👨‍🍳 PR Sous Chef · gpt54 · 9.44 AIC · ⊞ 8.3K ·

Comment /souschef to run again


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

Copilot AI linked an issue Aug 7, 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 Validate uv package names against PEP 508 before uv pip show Aug 7, 2026
Copilot AI requested a review from pelikhan August 7, 2026 06:16
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

PR Triage

Category: bug (security hardening) · Risk: medium · Score: 60/100 (impact 25, urgency 18, quality 17)

Recommendation: fast_track — grouped with #51017, #51002, #50995 (lint-quality-cleanup batch)

Validates uv package names against PEP 508 before shelling out to uv pip show — closes an injection-adjacent input-validation gap. Draft, CI pending, no reviews yet. Small (2 files, 33 additions). Recommend prioritizing due to security nature.

Generated by 🔧 PR Triage Agent · auto · 55.6 AIC · ⌖ 2.45 AIC · ⊞ 7.9K ·

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

Adds stricter uv package-name validation to prevent unsafe arguments reaching uv pip show.

Changes:

  • Validates extracted uv package names before command execution.
  • Adds regression coverage for malformed package names.
Show a summary per file
File Description
pkg/workflow/pip_validation.go Adds uv package-name validation and security annotation.
pkg/workflow/argument_injection_test.go Tests rejection of malformed uv package names.

Review details

Tip

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

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

Comment thread pkg/workflow/pip_validation.go Outdated

// Validate the package name against PyPI naming rules (PEP 508) before
// passing it as a command argument to uv (argument injection guard).
if err := validatePipPackageName(pkgName); err != nil {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 242d733: added stripUvPackageVersion which strips a trailing @version (in addition to ==version) before validation and before passing the name to uv pip show. Added TestValidateUvPackages_AcceptsVersionedUvxSpec to cover uvx ruff@0.1.0.

Comment thread pkg/workflow/argument_injection_test.go Outdated
Comment on lines +405 to +407
if _, err := fileutil.ResolveExecutablePath("uv"); err != nil {
t.Skip("uv not installed - skipping uv argument validation test")
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 242d733: package-name syntax validation (including the - prefix and PEP 508 checks) now runs before uv/pip are resolved or invoked, so TestValidateUvPackages_RejectsInvalidPackageName no longer skips when uv is absent.

Comment thread pkg/workflow/pip_validation.go Outdated
// passing it as a command argument to uv (argument injection guard).
if err := validatePipPackageName(pkgName); err != nil {
pipValidationLog.Printf("Invalid uv package name %s: %v", pkgName, err)
errors = append(errors, fmt.Sprintf("uv package '%s' is invalid: %v", pkg, err))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 242d733: invalid-name failures are now collected separately and returned immediately as a distinct NewValidationError with a message about PEP 508 naming rules, before any network/cache validation is attempted. They no longer share the "requires network access or a local cache" message.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Design Decision Gate 🏗️. Review the logs for details.

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

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for PR Code Quality Reviewer. Review the logs for details.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Matt Pocock Skills Reviewer. Review the logs for details.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

⚠️ Security scanning failed for Test Quality Sentinel. Review the logs for details.

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

The PEP 508 validation guard added to validateUvPackages is a good step. Existing review comments from a previous pass already flag the three issues worth addressing:

  1. Versioned uvx specs rejected (uvx ruff@0.1.0) — only == is stripped before the name check, so @-versioned specs (which extractUvFromCommands explicitly supports) will be incorrectly rejected.
  2. Test skips on hosts without uv — the invalid-name branch should never need uv; the skip guard makes the test non-deterministic. Remove the fileutil.ResolveExecutablePath guard from this particular test.
  3. Misleading aggregate error message — invalid-name failures are mixed into the same errors slice as network/cache failures, so the final NewValidationError may suggest retrying with a cache when the real fix is to rename the package.

No additional issues found beyond those already raised.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 13.1 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 three issues before merge.

📋 Key Themes & Highlights

Key Themes

  1. Versioned package spec regression (@-pinned specs like ruff@0.1.0 fail the new PEP 508 check because only == is stripped before validation)
  2. Flaky / skipped test — the regression test guards on the uv binary check before exercising validation logic, so it is a no-op in most CI environments
  3. Misleading error message — invalid-name errors are mixed into the same slice as network/cache failures, producing confusing user-facing messages

All three are captured in the existing inline comments. Please address them before merge.

Positive Highlights

  • ✅ Root cause correctly identified: validatePipPackageName now consistently applied to the uv path
  • #nosec G204 comment is accurate and well-scoped
  • ✅ PR description audit table is clear and helpful

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

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel Report - placeholder

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 · 59.2 AIC · ⊞ 7.7K ·
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: 95/100. 0% implementation tests (threshold: 30%). Both new/modified tests enforce PEP 508 argument injection prevention. 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: Request changes — the fix introduces a confirmed correctness regression

The added uv package-name validation correctly closes the argument-injection gap for malicious names, but it strips only == (pip-style) version specifiers before validating, not the @ version separator that uvx uses (e.g. ruff@0.1.0). This causes legitimate, previously-working uvx tool@version custom steps to be rejected at compile time — verified locally against validatePipPackageName.

Themes and additional concerns
  • Correctness (blocking): @-pinned uvx specs are now hard-rejected as "invalid package names" — a false positive that breaks working workflows.
  • UX/error clarity: invalid-name failures are folded into the same aggregate error as network/cache failures, producing a misleading "install uv/pip, check network" message for what is actually a rejected package name.
  • Test coverage gap: the new regression test skips entirely when uv isn't installed on the runner, so the injection guard may never be exercised in CI; the PR author flagged this same concern in the description without resolving it.

The security intent is sound and matches the existing pip guard pattern, but the @ handling gap must be fixed before merge, and the fallback/test-skip behavior needs a decision (hard-fail both paths, or make the test independent of uv availability).

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

Comment thread pkg/workflow/pip_validation.go Outdated
pkgName = pkg[:eqIndex]
}

// Validate the package name against PyPI naming rules (PEP 508) before

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.

This rejects valid versioned uvx specs like ruff@0.1.0 as invalid package names, since only pip-style == is stripped, not the @ version separator that extractUvFromCommands explicitly supports.

💡 Missing `@` version-specifier handling breaks legitimate uvx workflows

extractUvFromCommands (pkg/workflow/pip.go) produces package specs like ruff@0.1.0 for uvx ruff@0.1.0 (covered by the existing test uvx with version in pip_test.go). This new code only strips ==:

pkgName := pkg
if eqIndex := strings.Index(pkg, "=="); eqIndex > 0 {
    pkgName = pkg[:eqIndex]
}

pypiPackageNameRE (^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$) does not allow @, so validatePipPackageName("ruff@0.1.0") fails, and the workflow compile now hard-errors on a perfectly valid, previously-working uvx tool@version custom step. I verified this directly:

err=invalid pip package name: "ruff@0.1.0" — PyPI names must start and end with a letter or digit...

Fix: strip an @version suffix (in addition to ==) before validating, e.g.:

pkgName := pkg
if eqIndex := strings.Index(pkg, "=="); eqIndex > 0 {
    pkgName = pkg[:eqIndex]
} else if atIndex := strings.Index(pkg, "@"); atIndex > 0 {
    pkgName = pkg[:atIndex]
}

This is a real behavioral regression, not just a style nit — it will break existing CI workflows that pin uvx tool versions with @.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 242d733: added stripUvPackageVersion which strips a trailing @version (in addition to ==version) before validation, and covered uvx tool@version with a new regression test.

Comment thread pkg/workflow/pip_validation.go Outdated
// passing it as a command argument to uv (argument injection guard).
if err := validatePipPackageName(pkgName); err != nil {
pipValidationLog.Printf("Invalid uv package name %s: %v", pkgName, err)
errors = append(errors, fmt.Sprintf("uv package '%s' is invalid: %v", pkg, err))

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.

Invalid-name failures are merged into the same errors slice as network/cache failures, so the final aggregate error message (below) always claims validation "requires network access or a local cache", which is misleading for a plain naming violation.

💡 Aggregate error message is wrong for invalid-name failures

Further down in this function, NewValidationError is built from the combined errors slice with guidance like "validation requires network access or a local cache; install uv/pip...". A user hitting uvx pkg;whoami will be told to install uv/pip or check their network — completely unrelated to the actual problem (an invalid package name). This makes the actionable security rejection confusing and harder to diagnose, undermining the value of the new guard.

Suggested fix: track invalid-name errors in a separate slice/counter and surface a distinct, accurate error message for them (e.g. "invalid package name(s): ...") instead of folding them into the generic network/cache guidance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 242d733: invalid-name failures are now tracked separately and returned as a distinct error before any network/cache validation, with a message about PEP 508 naming rules instead of the generic network/cache guidance.

Comment thread pkg/workflow/argument_injection_test.go Outdated

// TestValidateUvPackages_RejectsInvalidPackageName verifies that uv package names
// which do not conform to the PyPI naming rules are rejected before being passed
// as arguments to the uv CLI.

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.

This regression test silently t.Skips in any CI environment without uv installed, meaning the argument-injection guard being added in this PR may never actually run in CI.

💡 Security regression test can silently never execute

validateUvPackages has two code paths: the uv-based path (where this PR adds the fix) and a pip-fallback path (validateUvPackagesWithPip) used when uv is absent, which the PR description itself says treats invalid names as warnings, not errors. If the CI runner lacks uv, this test — the only regression coverage for the new guard — skips entirely and gives false confidence that the injection is blocked.

Also note the PR author flagged this exact gap ("Note on the test guard... skips when uv is absent... Worth a second opinion on whether that fallback should also hard-fail") but shipped without resolving it.

Fix: don't gate the test on uv being present. Either (a) call validatePipPackageName directly to assert rejection independent of which downstream tool is available, or (b) make the pip-fallback path also hard-fail on invalid names so both paths are equally protected and testable without uv.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 242d733: package-name validation now runs before uv/pip are resolved or invoked, so the test no longer skips when uv is absent and reliably exercises the guard in CI.

@pelikhan

pelikhan commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Please address the remaining review feedback, refresh the branch if needed, and run the pr-finisher skill before handing back to maintainers.

Open review items to address:

Branch refresh was requested.

Run context: https://github.com/github/gh-aw/actions/runs/31185909757

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

…stic test

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Copilot AI requested a review from gh-aw-bot August 7, 2026 14:58
@pelikhan

pelikhan commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Please address the remaining review feedback, failed checks, refresh the branch if GitHub can update it cleanly, and run the pr-finisher skill before handing back to maintainers.

Open review items to address (newest first):

Failed checks:

Branch refresh was requested.

Run context: https://github.com/github/gh-aw/actions/runs/31192669795

Generated by 👨🍳 PR Sous Chef · gpt54
Comment /souschef to run again

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

Copilot AI commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Please address the remaining review feedback, failed checks, refresh the branch if GitHub can update it cleanly, and run the pr-finisher skill before handing back to maintainers.

Open revi...

Current head 304ca65 already contains the fixes from 242d733 plus the base refresh. I rechecked the two github-actions reviews; they refer to the same three issues already fixed in 242d733. The listed failed checks are stale on old head dbefd32: go test ./pkg/workflow, BASE_REF=e5e8f9d8beb757f182b32e095a1ffb1cab745060 make test-unit, and BASE_REF=e5e8f9d8beb757f182b32e095a1ffb1cab745060 make test all pass on 304ca65, including TestWasmGolden_AllEngines. The branch is already refreshed to the current PR base e5e8f9d.

@pelikhan
pelikhan merged commit a6e9b53 into main Aug 7, 2026
1 of 2 checks passed
@pelikhan
pelikhan deleted the copilot/sighthound-security-findings-again branch August 7, 2026 16:22
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.86.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