Repository navigation
Replace python-jose with PyJWT: drops ecdsa (an unfixable, ignored advisory) and two more packages from the image #422
Description
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 regeneraterequirements.lock.ecdsa,rsaandpyasn1should drop out; confirm with a dry-run install.- Swap
from jose import JWTError, jwtforimport jwtandjwt.PyJWTError(orjwt.InvalidTokenError), keeping the key-rotation loop as it is. - Remove
--ignore-vuln PYSEC-2026-1325and its row fromSECURITY_GATES.md§7e.
Things to check, not assume
- Existing tokens keep working. Tokens issued by
python-josebefore 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,
submust be a string, and it checks the types ofexp/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 JWTErrormust catch what PyJWT raises for expired, malformed and wrong-key tokens, including inws.py. - Key rotation. The loop in
backend/auth.pytries 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,rsaandpyasn1are absent from the image's install set (dry-run).pip-auditpasses with PYSEC-2026-1325 no longer ignored.- A token minted with
python-josebefore the change still authenticates after it. - The full backend suite passes.
Activity
fix(security): sign JWTs with PyJWT, and drop python-jose and ecdsa (#…