From 222c58e0e89ec1133ce5fd92deaf622e7abcf338 Mon Sep 17 00:00:00 2001 From: David Knowles Date: Mon, 5 Oct 2026 22:18:12 -0400 Subject: [PATCH 1/3] Bump virtualenv to 21.14.5 to fix Dependabot alerts virtualenv is a transitive dev dependency (via pre-commit) that only appears in uv.lock, so Dependabot never opened security update PRs for it -- the uv ecosystem can't bump lockfile-only transitive dependencies (dependabot/dependabot-core#16449). Four alerts accumulated as a result: - GHSA-p58f-9548-mpm2 (high): bash/fish activation scripts execute commands embedded in paths - GHSA-x78j-v8h9-3j2q (high): command injection via --prompt in activate.bat - GHSA-94p9-xgh2-xp45 (high): seed wheels not integrity-checked - GHSA-9h9j-4vrj-gf7g (medium): unsanitized prompt values written to pyvenv.cfg Ran `uv lock --upgrade-package virtualenv`, taking virtualenv from 21.7.0 to 21.14.5 (highest patched version required was 21.7.13) and python-discovery from 1.5.0 to 1.6.1 as a side effect. Co-Authored-By: Claude Opus 5 --- uv.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/uv.lock b/uv.lock index 7048910..d46c710 100644 --- a/uv.lock +++ b/uv.lock @@ -1114,15 +1114,14 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.5.0" +version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, - { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/51/276f964496a5714ab9f320896195639086881c2b39c03b5ad13de84acbb8/python_discovery-1.5.0.tar.gz", hash = "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef", size = 72483, upload-time = "2026-07-21T13:14:14.641Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/57/250bd238b966cece44328235eb85290045d059265fdaf7527a3a958123db/python_discovery-1.6.1.tar.gz", hash = "sha256:cf87d3627dfb4412437fdd5b13eae402607722998d21567993aedbc59b23c15e", size = 84338, upload-time = "2026-09-18T01:31:53.971Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl", hash = "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98", size = 34205, upload-time = "2026-07-21T13:14:13.398Z" }, + { url = "https://files.pythonhosted.org/packages/16/7d/e9ffbadfbf89c93848412d04594135c4ae8c1d37d9e053b9c3ed718fabc4/python_discovery-1.6.1-py3-none-any.whl", hash = "sha256:d43fcdef879fe795352bd13ccf8d185ba5a9f86f36cfcd00529f596e737442b3", size = 38664, upload-time = "2026-09-18T01:31:52.448Z" }, ] [[package]] @@ -1234,17 +1233,18 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.7.0" +version = "21.14.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, + { name = "packaging" }, { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/25/e367a7229b0914772ca8d81b41fde012d9feda68523b52644a571bb21ce8/virtualenv-21.7.0.tar.gz", hash = "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c", size = 5527510, upload-time = "2026-07-21T13:12:14.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/f9/f323b3b6058cff3853b31cf6a49c0425ed797bf9611572ec10d065e08bac/virtualenv-21.14.5.tar.gz", hash = "sha256:c4cb6c13e46b57225a999c7e22a09b163393878facc7ac4c059a57f46faa1647", size = 5467127, upload-time = "2026-10-02T17:42:25.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/7a/ae29312b1e88a22e81f5d21fc11526d2a114089776c2550d2b205b6c2a47/virtualenv-21.7.0-py3-none-any.whl", hash = "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd", size = 5507078, upload-time = "2026-07-21T13:12:12.136Z" }, + { url = "https://files.pythonhosted.org/packages/df/93/084f10ebbecdedf7902ed68fbecc903f0e9c5742155bfffb05eccbc071da/virtualenv-21.14.5-py3-none-any.whl", hash = "sha256:b0651e0174982bba17cc6f0aaa2ef496730cb85a3f35eb5d1119d1c904f7d1da", size = 5487482, upload-time = "2026-10-02T17:42:23.38Z" }, ] [[package]] From 04dff13eb4f8bf1c23709c024226157c7529c557 Mon Sep 17 00:00:00 2001 From: David Knowles Date: Mon, 5 Oct 2026 22:21:28 -0400 Subject: [PATCH 2/3] Pin virtualenv in the dev dependency group Dependabot's uv ecosystem cannot update a transitive dependency that exists only in uv.lock, which is why four virtualenv security alerts accumulated with no PR attached. Declaring it as a direct dev dependency makes Dependabot track it, so future advisories get an automatic PR instead of a silent alert. virtualenv remains a pre-commit dependency; the pin only duplicates what the lockfile already resolved, so the resolution is unchanged. Co-Authored-By: Claude Opus 5 --- pyproject.toml | 4 ++++ uv.lock | 2 ++ 2 files changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2807764..2a8bb1f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,4 +66,8 @@ test = [ dev = [ {include-group = "test"}, "pre-commit==4.6.2", + # Transitive dependency of pre-commit, pinned here only so Dependabot + # tracks it as a direct dependency: it cannot bump transitive packages + # that exist only in uv.lock. See dependabot/dependabot-core#16449. + "virtualenv==21.14.5", ] diff --git a/uv.lock b/uv.lock index d46c710..33d89c1 100644 --- a/uv.lock +++ b/uv.lock @@ -1019,6 +1019,7 @@ dev = [ { name = "pytest-aiohttp" }, { name = "pytest-asyncio" }, { name = "ruff" }, + { name = "virtualenv" }, ] test = [ { name = "freezegun" }, @@ -1047,6 +1048,7 @@ dev = [ { name = "pytest-aiohttp", specifier = "==1.1.1" }, { name = "pytest-asyncio", specifier = "==1.4.0" }, { name = "ruff", specifier = "==0.16.10" }, + { name = "virtualenv", specifier = "==21.14.5" }, ] test = [ { name = "freezegun", specifier = "==1.5.5" }, From 16b442eeed67701b5864d272a94da64dc2cea275 Mon Sep 17 00:00:00 2001 From: David Knowles Date: Mon, 5 Oct 2026 22:23:36 -0400 Subject: [PATCH 3/3] Add a weekly workflow that refreshes uv.lock Pinning virtualenv in the dev group fixes that one package, but every other transitive dependency has the same blind spot: Dependabot's uv ecosystem only updates what pyproject.toml declares, so a package living solely in uv.lock is never bumped and its advisories accumulate as alerts with no pull request (dependabot/dependabot-core#16449). This runs `uv lock --upgrade` every Monday and opens a PR when the resolution moves. Direct dependencies are all `==` pinned, so the refresh can only change transitive versions -- Dependabot keeps ownership of direct bumps and the two don't produce competing PRs. The PR body lists the version changes uv reported, mirroring how update-grills.yml summarises its own diff. Note the lockfile is 17 transitive packages behind as of this commit, so the first run will not be a small PR. Co-Authored-By: Claude Opus 5 --- .github/workflows/update-uv-lock.yml | 104 +++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .github/workflows/update-uv-lock.yml diff --git a/.github/workflows/update-uv-lock.yml b/.github/workflows/update-uv-lock.yml new file mode 100644 index 0000000..bcc4f27 --- /dev/null +++ b/.github/workflows/update-uv-lock.yml @@ -0,0 +1,104 @@ +name: Refresh uv.lock + +# Dependabot's uv ecosystem only updates dependencies declared in +# pyproject.toml; a transitive dependency that exists solely in uv.lock is +# never bumped, so its advisories pile up as alerts with no pull request +# attached (dependabot/dependabot-core#16449). This job re-resolves the +# lockfile weekly to pick those up. Direct dependencies are all `==` pinned, +# so `uv lock --upgrade` can only move transitive versions -- Dependabot +# still owns every direct bump. + +on: + schedule: + - cron: '0 0 * * 1' # Runs at 00:00 UTC every Monday + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +env: + PYTHON_VERSION: "3.12" + +jobs: + refresh-uv-lock: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + token: ${{ secrets.BOT_PAT }} + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v7 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install uv + run: | + python -m pip install --upgrade pip + python -m pip install uv + + - name: Refresh uv.lock + id: refresh + run: | + # uv reports each version change as "Updated vA -> vB" on + # stderr. Capture it so the pull request can say what moved rather + # than only that the lockfile changed. + UV_OUTPUT="$(mktemp)" + trap 'rm -f "$UV_OUTPUT"' EXIT + + # Redirect rather than pipe to tee: a pipeline would mask a + # non-zero exit from uv itself. + uv lock --upgrade >"$UV_OUTPUT" 2>&1 || { + status=$? + cat "$UV_OUTPUT" + echo "::error::uv lock --upgrade failed" + exit "$status" + } + cat "$UV_OUTPUT" + + if git diff --quiet -- uv.lock; then + echo "changes_detected=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "changes_detected=true" >> "$GITHUB_OUTPUT" + + # Assign before writing: `grep | sed` exits 0 even when grep + # matches nothing, so a `||` fallback on the pipeline never fires. + CHANGES="$(grep -E '^(Updated|Added|Removed) ' "$UV_OUTPUT" | sed 's/^/- /')" + if [[ -z "$CHANGES" ]]; then + CHANGES="- (uv reported no per-package changes)" + fi + + { + echo "summary<> "$GITHUB_OUTPUT" + + - name: Create PR + if: steps.refresh.outputs.changes_detected == 'true' + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ secrets.BOT_PAT }} + commit-message: "Automated: Refresh uv.lock" + title: "Automated: Refresh uv.lock" + body: | + Re-resolve uv.lock to pick up transitive dependency updates. + + ${{ steps.refresh.outputs.summary }} + + Direct dependencies are `==` pinned in pyproject.toml, so only + transitive versions can move here; Dependabot still opens the + pull requests for direct bumps. This job exists because + Dependabot cannot update a transitive dependency that appears + only in uv.lock (dependabot/dependabot-core#16449). + + This PR was automatically generated by a GitHub Action. + push-to-fork: dknowles-bot/pytboss + base: main + branch: uv-lock-${{ github.run_number }} + delete-branch: true + labels: dependencies