Skip to content

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

Closed

Description

@keilynrp

Why

python-jose is the only reason three packages are in the backend image. In the dry-run install of 2026-09-29, ecdsa, rsa and pyasn1 are required by python-jose alone (pyasn1 also by rsa).

One of them carries a permanent exception in the security gate. ecdsa 0.19.2, PYSEC-2026-1325 (CVE-2024-23342) is the Minerva timing attack on ECDSA signing. It affects every version, and upstream does not fix side channels in pure-Python code. The 2026-09-30 baseline review (#421, SECURITY_GATES.md §7e) found it unreachable: JWTs are HS256, decode pins the algorithm, and nothing imports ecdsa. But an unreachable, unfixable advisory in the shipped image is an exception to re-justify every quarter, for a library UKIP doesn't use.

UKIP uses only HS256 signing. PyJWT covers that with no dependencies (it pulls in cryptography only for asymmetric algorithms, and cryptography is already in the image).

Surface

It is small. Three files import jose:

File Uses
backend/auth.py jwt.encode ×2 (access and refresh tokens); jwt.decode(token, key, algorithms=[ALGORITHM]) in a loop over the configured keys (rotation); JWTError ×3
backend/routers/auth_users.py JWTError ×2 (refresh / token handling)
backend/routers/ws.py JWTError (WebSocket auth)

Plus 3 test files that import jose.

Proposal

  • requirements.txt: python-jose[cryptography] → PyJWT, and regenerate requirements.lock. ecdsa, rsa and pyasn1 should drop out; confirm with a dry-run install.
  • Swap from jose import JWTError, jwt for import jwt and jwt.PyJWTError (or jwt.InvalidTokenError), keeping the key-rotation loop as it is.
  • Remove --ignore-vuln PYSEC-2026-1325 and its row from SECURITY_GATES.md §7e.

Things to check, not assume

  • Existing tokens keep working. Tokens issued by python-jose before the deploy must still decode with PyJWT: same HS256, same keys, same claims. Test it with a token minted by the old library.
  • Claim validation differences. PyJWT validates some registered claims more strictly (for example, sub must be a string, and it checks the types of exp/iat/nbf). Our claims (sub = username, role, sid, exp) must pass. The existing auth tests, plus the test above, cover it.
  • Error type. Every except JWTError must catch what PyJWT raises for expired, malformed and wrong-key tokens, including in ws.py.
  • Key rotation. The loop in backend/auth.py tries each configured key and re-raises the last error. Behaviour must be unchanged when the first key fails and the second succeeds.

Acceptance

  • python-jose, ecdsa, rsa and pyasn1 are absent from the image's install set (dry-run).
  • pip-audit passes with PYSEC-2026-1325 no longer ignored.
  • A token minted with python-jose before the change still authenticates after it.
  • The full backend suite passes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendBackend / FastAPI API serverdependenciesDependency updates and managementsecuritySecurity, auth, privacy, compliance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions