Skip to content

fix(deps): close the advisories on the auth path and fail CI on new ones (D-01) - #228

Merged
ViperJuice merged 2 commits into
mainfrom
fix/224-dep-advisories
Sep 5, 2026
Merged

ViperJuice merged 2 commits into
mainfrom
fix/224-dep-advisories

Conversation

@ViperJuice

@ViperJuice ViperJuice commented Sep 5, 2026 •

Copy link
Copy Markdown
Collaborator

First item from the 2026-09-01 codebase review (#227), D-01. See #224.

Why this is first, not tenth. The review lists D-01 as "refresh uv.lock", but pip-audit on the shipped lockfile reported nine advisories, and the load-bearing one is PYSEC-2026-176 in PyJWT 2.10.1 — a verifier-side algorithm allow-list bypass in jwt.decode(). That is the exact call at src/pmcp/auth.py:512 (jwt.decode(..., algorithms=list(allowed_algorithms))), and the exact control SECURITY.md promises:

Signatures are only accepted for the operator-configured algorithm allowlist (default RS256/ES256); the token's own alg header is never trusted.

Also closed, all adjacent to this repo's own auth work:

package advisories why it matters here
pyjwt 2.10.1 → 2.13.0 PYSEC-2026-175/176/177/178/179, 120 176 = allowlist bypass; 175 = PyJWKClient passes its URI straight to urllib.request.urlopen (next door to #210/#211); 177 = unknown-kid forces a JWKS refetch — this review's own S-07, reachable through the dependency too
cryptography 46.0.3 → 50.0.1 PYSEC-2026-3554, GHSA-537c-gmf6-5ccf performs the signature verification
aiohttp 3.13.2 → 3.14.3 PYSEC-2026-3546/3547 the JWKS fetch client (the review's S-08)
starlette 0.50.0 → 1.6.0 PYSEC-2026-161/248/249/2280/2281 the HTTP surface. No fix exists inside 0.x
python-multipart, python-dotenv, click, pytest 9 more —

pip-audit now reports No known vulnerabilities found.

Floors, not just the lock. aiohttp>=3.14.2 and pyjwt>=2.13.0 are declared. A floor that still admits the vulnerable range is not a pin — the same unbounded-dependency class that has bitten this repo before.

Targeted upgrades, deliberately not uv lock --upgrade. The blanket form also pulled ruff 0.14.10 → 0.16.6, whose new default rules failed ~40 pre-existing files under scripts/probes/ — unrelated churn inside a security fix. ruff is held at 0.14.10; only the eight flagged packages moved.

New blocking audit job. pip-audit --strict against the resolved environment (uv.lock is what ships and what advisories are scored against). Blocking rather than advisory because this repo's guard culture is fail-closed (#187, #189, #202, #217) and the whole lesson of D-01 is that an advisory on the auth path went unnoticed until a manual review. The cost — a newly published advisory can redden an unrelated PR — is the decision we want forced.

Its two new remote uses: raise the pinned-ref count guard from 30 to 32. That guard firing was correct: a new remote action is meant to be spelled twice.

Verification (each run in the step it is reported): pip-audit clean; full suite 3199 passed, with failures/errors/skips/deselects identical to main — including across the starlette 0.x→1.x and cryptography 46→50 major bumps (the 107 failed / 106 errors are the documented /tmp/package.json npm-identity host quirk, unrelated); ruff check, ruff format --check, mypy src/, and scripts/check_workflows.py all exit 0; workflow-guard suite 194 passed.

🤖 Generated with Claude Code

https://claude.ai/code/session_018NR2LKqZgn7CXVH2816gaa


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…nes (D-01)

pip-audit reported nine advisories against the shipped lockfile. The load-
bearing one: PyJWT 2.10.1 carries PYSEC-2026-176, a verifier-side algorithm
allow-list bypass in jwt.decode() -- which is the exact call at
src/pmcp/auth.py:512 and the exact control SECURITY.md promises. Also closed
PYSEC-2026-175 (PyJWKClient -> urlopen) and PYSEC-2026-177 (unknown-kid JWKS
refetch, this review's own S-07), plus cryptography (does the signature
verification), aiohttp (the JWKS fetch client), starlette, python-multipart,
python-dotenv, click and pytest.

Floors raised, not just the lock: aiohttp>=3.14.2 and pyjwt>=2.13.0. A floor
that still admits the vulnerable range is not a pin -- the same unbounded-
dependency class that has bitten this repo before.

Upgrades are TARGETED, not 'uv lock --upgrade'. The blanket form also pulled
ruff 0.14.10 -> 0.16.6, whose new default rules failed ~40 pre-existing files
in scripts/probes -- unrelated churn in a security fix. ruff is held.

New blocking 'audit' job runs pip-audit --strict on the resolved environment.
Blocking rather than advisory: this repo's guard culture is fail-closed, and
the whole lesson of D-01 is that an advisory on the auth path sat unnoticed.
Its two new remote uses: raise the pinned-ref count guard 30 -> 32, which is
the deliberate step that guard exists to force.
Board finding (codex, blocking): the audit job ran 'uv sync --all-extras --dev'
and 'uv run --with pip-audit', neither with --locked. Both silently RE-RESOLVE
when uv.lock is stale against pyproject.toml, so the job would audit a freshly
resolved graph, report clean, and leave the vulnerable lock that actually ships
untouched -- a false green in the one job whose entire purpose is to fail
closed. Verified: with a drifted floor, 'uv sync --locked' refuses ('To update
the lockfile, run uv lock') instead of re-resolving.

pip-audit is now pinned (==2.9.0). An unpinned tool fetched at run time is the
same supply-chain shape #217 closed for actions; the advisory DATA is still
fetched fresh, which is the part that must not be pinned.
@ViperJuice
ViperJuice merged commit aa2de50 into main Sep 5, 2026
14 checks passed
@ViperJuice
ViperJuice deleted the fix/224-dep-advisories branch September 5, 2026 17:22
ViperJuice added a commit that referenced this pull request Sep 10, 2026
Six advisories were published against the pinned httpx2/httpcore2 2.9.1 in the
last two days, so `main` started failing its own audit gate without anyone
changing code — which is exactly what that gate (added in #228) exists to do.
Every open PR inherits the red X, including the TRUST phase in #240.

  httpx2    2.9.1  PYSEC-2026-3845/3846/3847/3848/3849  fixes 2.10.0/2.11.0/2.12.0
  httpcore2 2.9.1  PYSEC-2026-3844                      fix   2.10.0

Lockfile only. `pyproject.toml:86` already declares `httpx2>=2.5.0,<3.0.0` and
2.12.0 sits inside it, so no constraint moves.

Scoped with `--upgrade-package` for each flagged package rather than a bare
`uv lock --upgrade`, which over-reached in this repo before and dragged ruff
forward, breaking ~40 unrelated files under scripts/probes/. Exactly three lock
entries changed:

  httpcore2       2.9.1 -> 2.12.0
  httpx2          2.9.1 -> 2.12.0
  httpx2-jsfetch  (new) 1.0

The new transitive was checked rather than waved through — a novel package
appearing under an HTTP library is the shape this repo's current programme is
about. It is declared by httpx2 2.12.0 itself, comes from PyPI with sdist and
wheel hashes, and is gated `sys_platform == 'emscripten'`, so it never installs
off Pyodide. Verified empirically: not importable after `uv sync --locked`.

Verification:
- `pip-audit --strict` on the resolved environment: "No known vulnerabilities
  found" (was six).
- Full suite, 3456 collected — identical population to main, no new tests.
  3215 passed + 107 failed + 106 errors = 3428, exactly main's passed count.
- The failures are the known worktree-location artifact: `node_modules` at the
  volume root trips npm's local-prefix rule and the resolver refuses by design
  (#195). Every failure reason traces to that refusal or its downstream
  UpdateServerOutput cascade, and the output contains zero occurrences of
  httpx, httpcore, ConnectError, ReadError or TransportError — so the transport
  bump introduced none of them. A checkout outside /mnt runs clean, as CI does.


Claude-Session: https://claude.ai/code/session_018FncHYooSXexECJ2DYcnu6

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant