Skip to content

Regenerate backend lockfiles to clear pip-audit failures, and assert they match pyproject - #292

Merged
d3mocide merged 2 commits into
mainfrom
claude/backend-dep-audit-fixes
Sep 16, 2026
Merged

d3mocide merged 2 commits into
mainfrom
claude/backend-dep-audit-fixes

Conversation

@d3mocide

@d3mocide d3mocide commented Sep 14, 2026 •

Copy link
Copy Markdown
Owner

The Backend — lint, type-check & tests job has failed at the pip-audit step on every run since Aug 10 — on main (run 31128880028 at d49e0b6) and on every open PR (#286, #287, #288, #289, #290, #291). Because the audit runs before Tests, the backend test suite has not actually executed in CI on any of them.

Correction to the original description: it claimed five packages move and that nothing else changed. That was wrong — my verification grep used a character class that didn't match extras, so it silently skipped uvicorn[standard]. uvicorn also moves, 0.51.0 → 0.52.0. The complete list is below, re-derived with a pattern that matches extras. The second commit also adds pip-tools and the CI check, which the original description predates.

Advisories cleared

Package Before After Advisory
aiohttp 3.14.1 3.14.3 PYSEC-2026-3545, PYSEC-2026-3546, PYSEC-2026-3547
cryptography 49.0.0 50.0.1 PYSEC-2026-3552
pip 26.1.2 26.2.1 PYSEC-2026-3721

aiohttp and cryptography are transitive (litellm, pywebpush, argon2-cffi), so they only move in the lock.

The pip entry is worth understanding: pip-compile records it under --allow-unsafe because pip-api and pip-tools require it, and pip install --require-hashes -r requirements-dev.lock was then downgrading the runner's own pip — the CI log shows Found existing installation: pip 26.2.1 → Successfully installed pip-26.1.2, i.e. the lockfile was actively installing the vulnerable version over a patched one.

pip-tools 7.6.0 → 7.6.1 (fallout from the pip bump)

Pinning pip 26.2.1 broke the dev toolchain: pip-tools 7.6.0 imports stdlib_pkgs from pip._internal.utils.compat, which pip 26.2 removed, so pip-compile dies on import:

ImportError: cannot import name 'stdlib_pkgs' from 'pip._internal.utils.compat'

Nothing in CI caught this, because CI never ran pip-compile — only pip install, ruff, mypy, pip-audit and pytest. pyproject.toml now floors pip-tools>=7.6.1 with the reason inline so the constraint survives the next regeneration.

The lock was stale vs. pyproject

Recompiling moves three more packages that I did not ask to upgrade:

Package Lock had pyproject declares
fastapi 0.139.0 fastapi==0.141.1
litellm 1.92.0 litellm==1.94.1
uvicorn[standard] 0.51.0 uvicorn[standard]==0.52.0

These aren't upgrades so much as the lock catching up. Dependabot has been bumping pyproject.toml (#280 and the fastapi/uvicorn bumps before it) without regenerating the lockfiles, which were last rebuilt at 26c434a. Since CI installs the lock and then runs pip install --no-deps -e ., the --no-deps means pyproject's pins are never enforced — so CI and the built image have been running fastapi 0.139.0 / litellm 1.92.0 / uvicorn 0.51.0 while pyproject declared 0.141.1 / 1.94.1 / 0.52.0.

Merging this is the point at which those declared versions actually take effect. I verified the suite against them (below).

Complete set of version changes — seven in requirements-dev.lock, five in requirements.lock, nothing else:

aiohttp            3.14.1  -> 3.14.3    CVE
cryptography       49.0.0  -> 50.0.1    CVE
pip                26.1.2  -> 26.2.1    CVE          (dev lock only)
pip-tools          7.6.0   -> 7.6.1     pip 26.2 compat (dev lock only)
fastapi            0.139.0 -> 0.141.1   lock catching up to pyproject
litellm            1.92.0  -> 1.94.1    lock catching up to pyproject
uvicorn[standard]  0.51.0  -> 0.52.0    lock catching up to pyproject

New CI step: Check lockfiles match pyproject

Added to the backend job to stop this recurring. It re-runs both pip-compile commands and fails if the result differs from what's committed. Since pip-compile without --upgrade keeps whatever the lock already pins, the re-run is a no-op until pyproject and the locks disagree.

It runs last, after Tests, so a drift failure never masks lint/type/test results — the opposite of the ordering problem that hid the test suite for two months.

Worth considering separately: Audit dependencies currently sits before Tests, which is why a dependency CVE has been hiding whether the code works. Moving Tests above it would decouple the two. I left the ordering alone since it wasn't asked for.

Testing

Clean Python 3.12 venv, installed exactly as CI does (pip install --require-hashes -r requirements-dev.lock && pip install --no-deps -e .):

  • pip-audit — No known vulnerabilities found (was 10 across 3 packages)
  • ruff check . — All checks passed
  • mypy luma --ignore-missing-imports — Success, 78 source files
  • pytest -x -q — 201 passed

Drift check, both directions:

  • A plain re-run of both pip-compile commands under the locked toolchain is byte-identical to the committed locks, so the step is a no-op on a healthy tree.
  • Introducing a pyproject-only bump (orjson 3.11.9 → 3.11.8) makes the step fail as intended.

Since cryptography is a major bump (49 → 50) and underpins auth and push, I smoke-tested those paths directly on the new version: Argon2id hash/verify round-trip, VAPID keypair generation, pywebpush import, and PyJWT HS256 encode/decode all work.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KqXjgMRArAxubgd5f5UYgC

The backend CI job has failed at the `pip-audit` step on every run since
Aug 10, on main and on every open PR, blocking the `Tests` step behind it.
pip-audit reported 10 advisories across three pinned packages:

  aiohttp      3.14.1 → 3.14.3  (PYSEC-2026-3545/3546/3547)
  cryptography 49.0.0 → 50.0.1  (PYSEC-2026-3552)
  pip          26.1.2 → 26.2.1  (PYSEC-2026-3721)

The pip pin is the odd one: pip-compile records it under --allow-unsafe
because pip-api and pip-tools require it, and `pip install --require-hashes`
was then *downgrading* the runner's own pip to the vulnerable version.
Bumping the pin is enough — dropping it would need the --allow-unsafe flag
to change, which is a bigger call than this fix warrants.

Recompiling also pulls fastapi 0.139.0 → 0.141.1 and litellm 1.92.0 →
1.94.1. Those are not upgrades so much as the lock catching up: pyproject
has pinned 0.141.1 and 1.94.1 since #280, but the lockfiles were last
regenerated at 26c434a. Because CI installs the lock and then runs
`pip install --no-deps -e .`, the stale lock silently won and the declared
pins never took effect.

Regenerated with:

  pip-compile --allow-unsafe --generate-hashes \
    --output-file=requirements.lock pyproject.toml \
    --upgrade-package aiohttp --upgrade-package cryptography
  pip-compile --allow-unsafe --extra=dev --generate-hashes \
    --output-file=requirements-dev.lock pyproject.toml \
    --upgrade-package aiohttp --upgrade-package cryptography \
    --upgrade-package pip

No other package versions moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KqXjgMRArAxubgd5f5UYgC
Pinning pip 26.2.1 in the previous commit broke the dev toolchain: pip-tools
7.6.0 imports stdlib_pkgs from pip._internal.utils.compat, which pip 26.2
removed, so `pip-compile` dies on import. It went unnoticed because CI never
ran pip-compile — only pip install, ruff, mypy, pip-audit and pytest.

pyproject now floors pip-tools at >=7.6.1 with the reason inline, so the
constraint survives the next regeneration rather than being rediscovered.

Adds a `Check lockfiles match pyproject` step to the backend job. CI installs
`--require-hashes` from the lock and then `--no-deps -e .`, so pyproject's
pins are never enforced — a bump that edits pyproject without regenerating
the locks silently does nothing and CI keeps testing the old versions. That
is how fastapi and litellm drifted two and three releases behind their
declared pins. Since pip-compile without --upgrade keeps whatever the lock
already pins, the re-run is a no-op until the two disagree.

The step runs last so a drift failure never masks lint, type or test results.

Verified: plain re-run of both pip-compile commands is byte-identical to the
committed locks; introducing a pyproject-only bump makes the step fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KqXjgMRArAxubgd5f5UYgC
@d3mocide d3mocide changed the title Regenerate backend lockfiles to clear pip-audit failures Regenerate backend lockfiles to clear pip-audit failures, and assert they match pyproject Sep 14, 2026
@d3mocide
d3mocide marked this pull request as ready for review September 16, 2026 14:52
@d3mocide
d3mocide merged commit 9184cee into main Sep 16, 2026
4 checks passed
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