Skip to content

fix(security): use uv run for pip-audit and bandit in python-publish-pypi.yml#49

Merged
williaby merged 3 commits into
mainfrom
fix/publish-pypi-pip-audit-scope
May 3, 2026
Merged

fix(security): use uv run for pip-audit and bandit in python-publish-pypi.yml#49
williaby merged 3 commits into
mainfrom
fix/publish-pypi-pip-audit-scope

Conversation

@williaby
Copy link
Copy Markdown
Collaborator

@williaby williaby commented May 2, 2026

Summary

  • Replace `uvx pip-audit` with `uv run --with pip-audit==2.10.0 pip-audit --strict` in the pre-publish security gate
  • Replace `uvx bandit` with `uv run --with 'bandit[toml]==1.9.4' bandit` for consistency
  • Pin tool versions explicitly to lock security gate behavior across cache evictions

Root cause

`uvx` is an alias for `uv tool run --no-project`, which runs the tool in an isolated throwaway environment with no access to the project's virtual environment or `uv.lock`. This means `uvx pip-audit` effectively audits an empty environment and will always pass; the security gate provides false confidence and will never catch a vulnerable project dependency.

`uv run --with` installs the specified tool into a temporary overlay on top of the project's `.venv`, giving pip-audit visibility into all packages that will actually ship to PyPI. The `--with` form is used rather than bare `uv run` so the workflow works for all downstream callers regardless of whether they list pip-audit and bandit as explicit dev dependencies.

Why `--strict`

`pip-audit --strict` fails if dependency collection fails on any single package, rather than silently skipping unresolvable packages. For a publish gate this is the correct posture: an unresolvable dependency is itself a risk signal.

Prior art

The same fix was applied to `python-ci.yml` in commit `2243166` ("fix(ci): migrate pip-audit from uvx to uv run for correct dependency scope"). This PR closes the same gap in the publish workflow, which is a higher-stakes gate.

Static analysis warnings

One SonarQube diagnostic fires on the new lines (S8541). It is a false positive in this context:

  • S8541 (`--no-build`): pip-audit is auditing the already-installed project venv, so no new packages are being built. Adding `--no-build` would reduce audit coverage by skipping packages that require a build step.

S8544 (unlocked versions) is now addressed by explicit version pinning in the `--with` flags (`pip-audit==2.10.0`, `bandit[toml]==1.9.4`).

Test plan

  • Trigger `python-publish-pypi.yml` via `workflow_dispatch` on a downstream repo that has pip-audit and bandit as dev dependencies; confirm the security check step passes and shows project dependency audit output
  • Trigger `python-publish-pypi.yml` via `workflow_dispatch` on a downstream repo that does NOT have pip-audit and bandit as dev dependencies; confirm the security check step still passes (validates the `--with` portability fix)
  • Confirm a downstream repo with a known vulnerable dependency fails the gate

Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

Warning

Rate limit exceeded

@williaby has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 9 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3638041a-e16b-48df-8aa4-fb4621d68a48

📥 Commits

Reviewing files that changed from the base of the PR and between 901c119 and ae9c695.

📒 Files selected for processing (2)
  • .github/workflows/python-publish-pypi.yml
  • CHANGELOG.md
📝 Walkthrough

Walkthrough

The Python PyPI publish workflow's security checks step now invokes pip-audit and bandit via uv run with --strict mode for pip-audit, replacing the previous uvx-based invocation method.

Changes

CI Security Checks

Layer / File(s) Summary
Workflow Command Invocation
.github/workflows/python-publish-pypi.yml
Security check steps replace uvx pip-audit and uvx bandit with uv run pip-audit --strict and uv run bandit -r "$SRC_DIR" -c pyproject.toml -ll.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes


🐰 A hop and a skip through the workflows so neat,
uv run now handles the security beat,
Bandit and pip-audit stand side by side,
With --strict the code has nowhere to hide! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description includes a clear summary, root cause analysis, rationale, prior art reference, static analysis notes, and test plan, but does not follow the repository's required template structure with sections like Type of Change, Changes Made, Testing, and Checklist. While the description contains substantial technical detail, consider using the repository's standard PR template to ensure consistency, include explicit Type of Change selection, and verify Testing and Checklist sections are completed before merge.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: replacing uvx with uv run for pip-audit and bandit in the publish workflow file.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/publish-pypi-pip-audit-scope

Tip

💬 Introducing [Slack Agent](https://www.coderabbit.ai/agent): Turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value).


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 10 minutes and 9 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@williaby
Copy link
Copy Markdown
Collaborator Author

williaby commented May 3, 2026

@github-copilot review

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the reusable PyPI publishing workflow’s pre-publish security gate to run scanners in the project context (via uv run) instead of an isolated throwaway environment (uvx), so dependency auditing reflects what would actually be published.

Changes:

  • Switch pip-audit invocation from uvx pip-audit to uv run pip-audit --strict.
  • Switch bandit invocation from uvx bandit ... to uv run bandit ... for consistency.

@@ -95,8 +95,8 @@ jobs:
SRC_DIR: ${{ inputs.source-directory }}
run: |
echo "Running pre-publish security checks..."
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in ae9c695: switched to uv run --with pip-audit==2.10.0 pip-audit --strict and uv run --with 'bandit[toml]==1.9.4' bandit .... The --with flag installs each tool into a temporary overlay on top of the project venv, preserving correct dependency scope while working for all downstream callers regardless of their dev dependencies.

@williaby
Copy link
Copy Markdown
Collaborator Author

williaby commented May 3, 2026

PR Review

9 findings (3 Critical, 3 Important, 2 Suggested, 1 Informational)


Critical (must fix before merge)

  • [Bug] python-publish-pypi.yml:98-99: uv run pip-audit and uv run bandit require these tools in the project's synced venv. No uv sync step precedes this. Downstream callers without pip-audit/bandit as dev dependencies will hard-fail on every publish -- a regression from uvx. Confirmed by Copilot (line 97 comment) and prior PR fix(security): eliminate input injection vectors and harden supply chain #46 review comment.
    Fix: uv run --with pip-audit pip-audit --strict / uv run --with 'bandit[toml]' bandit -r "$SRC_DIR" -c pyproject.toml -ll

  • [CLAUDE.md] CHANGELOG.md: Present in the repo but absent from CHANGED_FILES. A fix: commit requires a ### Fixed entry under [Unreleased] per CLAUDE.md.

  • [Writing] PR body: Two em-dashes () violate the hard no-em-dash rule in CLAUDE.md. Replace with commas or semicolons before merge.


Important (should fix)

  • [Bug] python-publish-pypi.yml:98: pip-audit --strict blocks publish when dependency collection fails (not just vulnerabilities). Callers with non-standard layouts may see opaque publish failures unrelated to security.

  • [Security] python-publish-pypi.yml:98-99: pip-audit and bandit versions are not pinned. In a security gate, tool version drift after cache eviction can silently change pass/fail behavior. Add explicit version pins to the --with flags.

  • [Accuracy] PR body: The claim that "pip-audit and bandit are project dev dependencies pinned in uv.lock" is only true for callers following this convention -- the reusable workflow does not enforce it. The S8544 justification is misleading for the general case.


SonarQube: not configured for this repository.

Copilot review requested; see Reviewers section for the inline comment at line 97.

🤖 Generated with Claude Code

williaby and others added 3 commits May 2, 2026 18:51
uvx pip-audit runs in an isolated throwaway environment with no access
to the project venv or uv.lock, so it audits nothing meaningful and will
always pass regardless of project dependencies. uv run pip-audit runs
inside the project venv and audits the packages that will actually ship.

The --strict flag causes pip-audit to fail if any dependency cannot be
resolved, rather than silently skipping it -- the correct posture for a
pre-publish gate.

This is the same fix applied to python-ci.yml in the architecture
cleanup (commit 2243166). Closes the same gap in the publish workflow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ion pins

Replace bare `uv run pip-audit --strict` and `uv run bandit` with
`uv run --with` invocations that include explicit version pins. The
previous form required both tools to be listed as project dev
dependencies in every downstream caller's uv.lock; callers without
them would hard-fail the publish gate or audit an empty environment.

Using `--with pip-audit==2.10.0` and `--with 'bandit[toml]==1.9.4'`
installs each tool into a temporary overlay on the project venv,
preserving correct dependency scope for all callers and locking
tool behavior across cache evictions.

Closes Copilot comment on PR #49.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@williaby williaby force-pushed the fix/publish-pypi-pip-audit-scope branch from 901c119 to ae9c695 Compare May 3, 2026 01:52
@williaby
Copy link
Copy Markdown
Collaborator Author

williaby commented May 3, 2026

PR Fix Summary

Addressed 4 of 9 findings (3 Critical, 1 Important fixed):

Workflow fix (ae9c695):

  • Replaced uv run pip-audit --strict with uv run --with pip-audit==2.10.0 pip-audit --strict
  • Replaced uv run bandit with uv run --with 'bandit[toml]==1.9.4' bandit
  • The --with form works for all downstream callers regardless of dev dependencies; version pins lock security gate behavior across cache evictions

CHANGELOG (b82a616-rebased):

  • Added ### Fixed entry describing the portability fix

PR body: Updated to remove em-dashes (CLAUDE.md compliance) and align the Static analysis section with the actual --with implementation

Copilot thread: Replied with fix details


Remaining (informational, no code change needed):

  • --strict semantics note: callers with unusual layouts (no pyproject.toml, empty projects) may see collection failures; this is intentional behavior for a publish gate
  • Prior art commit 2243166 in PR description is a dangling object; not reachable via GitHub UI but verifiable in the local object store

Pre-commit: not configured in this repo. CI re-run triggered by push.

🤖 Generated with Claude Code

@williaby williaby merged commit 42c83d7 into main May 3, 2026
4 checks passed
@williaby williaby deleted the fix/publish-pypi-pip-audit-scope branch May 3, 2026 07:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants