Skip to content

fix(security): sign JWTs with PyJWT, and drop python-jose and ecdsa (#422) - #423

Merged
keilynrp merged 1 commit into
mainfrom
fix/replace-python-jose-with-pyjwt
Sep 30, 2026
Merged

keilynrp merged 1 commit into
mainfrom
fix/replace-python-jose-with-pyjwt

Conversation

@keilynrp

Copy link
Copy Markdown
Collaborator

Closes #422.

Why

python-jose was the only reason ecdsa, rsa and pyasn1 were in the backend image. ecdsa carried PYSEC-2026-1325 (CVE-2024-23342, the Minerva timing attack on ECDSA signing), and no version fixes it. So it sat in the pip-audit baseline as a permanent exception, re-justified every quarter, for a library UKIP never used. UKIP signs HS256 only, which PyJWT covers with no dependencies.

Changes

Code backend/auth.py, routers/auth_users.py and routers/ws.py import PyJWT. PyJWTError keeps the JWTError name, so the six except sites catch every refusal as before. The key-rotation loop is unchanged
Dependencies PyJWT==2.15.1 in; python-jose, ecdsa, rsa and pyasn1 out
Security gate PYSEC-2026-1325 is removed, not ignored. pip-audit: No known vulnerabilities found, 6 ignored (chromadb and oauthlib only)

Nobody is signed out

test_jwt_library.py pins an access token and a refresh token minted by python-jose 3.5.0 with production's claims; the refresh token is signed with a retiring key. Both decode under PyJWT.

The same file checks that:

  • every refusal raises the type the call sites catch (expired, unknown key, tampered, unsigned alg: none, HS512 with the real key, garbage);
  • only HS256 is accepted;
  • nothing in backend/ imports jose.

Verified

  • The JWT, SSO and session tests (55) pass with python-jose uninstalled.
  • The lock matches the real image, checked the way the lock's header requires: built Dockerfile.backend and compared its pip freeze.
    • Nothing is installed that the lock doesn't pin.
    • PyJWT is in, and the four packages are out.
    • The only change to the lock-only set is those four leaving it.
  • Pre-push: full backend suite, 4408 passed / 10 skipped. Changed-file Ruff gate 0; lint ratchet exact.
  • CI green

🤖 Generated with Claude Code

…422)

python-jose was the only reason ecdsa, rsa and pyasn1 were in the backend
image, and ecdsa carried PYSEC-2026-1325 (CVE-2024-23342, the Minerva timing
attack on ECDSA signing): no version fixes it, so it sat in the pip-audit
baseline as a permanent exception, re-justified every quarter for a library
UKIP never used. UKIP signs HS256 only, which PyJWT covers with no
dependencies.

- backend/auth.py, routers/auth_users.py, routers/ws.py: import PyJWT, and
  PyJWTError under the JWTError name the six call sites already catch, so
  every refusal (expired, wrong key, tampered, unsigned, another algorithm,
  garbage) lands where it did. The key-rotation loop is unchanged.
- requirements.txt: PyJWT instead of python-jose[cryptography].
  requirements.lock: PyJWT==2.15.1 in, python-jose, ecdsa, rsa and pyasn1
  out. Verified the way the lock's header requires: built Dockerfile.backend
  and compared its pip freeze. Nothing is installed that the lock does not
  pin, PyJWT is in, the four are out, and the only change to the lock-only
  set is those four leaving it.
- security.yml and SECURITY_GATES.md §7e: PYSEC-2026-1325 removed rather
  than ignored; pip-audit reports "No known vulnerabilities found, 6 ignored"
  without it.

Nobody is signed out: test_jwt_library.py pins an access token and a refresh
token (the latter signed with a retiring key) minted by python-jose 3.5.0
with production's claims, and both decode under PyJWT. It also checks every
refusal raises the type the call sites catch, that only HS256 is accepted,
and that nothing in backend/ imports jose. The JWT, SSO and session tests
(55) pass with python-jose uninstalled.

Closes #422.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@keilynrp
keilynrp force-pushed the fix/replace-python-jose-with-pyjwt branch from 3a3de91 to cc3c232 Compare September 30, 2026 19:50
@keilynrp
keilynrp merged commit e165fa0 into main Sep 30, 2026
53 checks passed
@keilynrp
keilynrp deleted the fix/replace-python-jose-with-pyjwt branch September 30, 2026 20:19
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.

Replace python-jose with PyJWT: drops ecdsa (an unfixable, ignored advisory) and two more packages from the image

1 participant