fix(security): replace python-jose with PyJWT to eliminate ecdsa CVEs

Snyk scan found 3 High severity vulns: two in ecdsa (pulled by python-jose)
and one in diskcache (pulled by pySigma, never imported). Remove both
vulnerable dependencies and migrate JWT handling to PyJWT. Fix
test_logout_revokes_token which broke because test stubs sys.modules[jose]
with a MagicMock at collection time; test now uses PyJWT directly.
This commit is contained in:
kitos
2026-06-11 09:06:16 +02:00
parent c99cc4946a
commit 4e378540af
6 changed files with 18 additions and 15 deletions
+3 -3
View File
@@ -2,7 +2,7 @@
This module provides pure functions for: This module provides pure functions for:
- Hashing and verifying passwords using bcrypt via passlib. - Hashing and verifying passwords using bcrypt via passlib.
- Creating JWT access tokens using python-jose. - Creating JWT access tokens using PyJWT.
- Managing a Redis-backed token blacklist for revocation. - Managing a Redis-backed token blacklist for revocation.
No endpoints are defined here. No endpoints are defined here.
@@ -17,8 +17,8 @@ import uuid as _uuid
# Import datetime, timedelta, timezone from datetime # Import datetime, timedelta, timezone from datetime
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
# Import jwt from jose # Import jwt (PyJWT)
from jose import jwt import jwt
# Import CryptContext from passlib.context # Import CryptContext from passlib.context
from passlib.context import CryptContext from passlib.context import CryptContext
+4 -4
View File
@@ -19,8 +19,8 @@ from fastapi import Cookie, Depends, HTTPException, status
# Import OAuth2PasswordBearer from fastapi.security # Import OAuth2PasswordBearer from fastapi.security
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
# Import JWTError, jwt from jose # Import jwt (PyJWT)
from jose import JWTError, jwt import jwt
# Import Session from sqlalchemy.orm # Import Session from sqlalchemy.orm
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -119,8 +119,8 @@ async def get_current_user(
if jti and auth_lib.is_token_blacklisted(jti): if jti and auth_lib.is_token_blacklisted(jti):
# Raise revoked_exception # Raise revoked_exception
raise revoked_exception raise revoked_exception
# Handle JWTError # Handle any JWT validation error (expired, invalid signature, malformed)
except JWTError: except jwt.exceptions.InvalidTokenError:
# Raise credentials_exception # Raise credentials_exception
raise credentials_exception raise credentials_exception
+4 -4
View File
@@ -16,8 +16,8 @@ from fastapi import APIRouter, Cookie, Depends, Request, Response
# Import OAuth2PasswordRequestForm from fastapi.security # Import OAuth2PasswordRequestForm from fastapi.security
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
# Import JWTError, jwt from jose # Import jwt (PyJWT)
from jose import JWTError, jwt import jwt
# Import Session from sqlalchemy.orm # Import Session from sqlalchemy.orm
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -234,8 +234,8 @@ def logout(
if jti: if jti:
# Call blacklist_token() # Call blacklist_token()
blacklist_token(jti, float(exp)) blacklist_token(jti, float(exp))
# Handle JWTError # Handle any JWT validation error during logout (token may be expired or malformed)
except JWTError: except jwt.exceptions.InvalidTokenError:
# Intentional no-op placeholder # Intentional no-op placeholder
pass pass
+1 -2
View File
@@ -3,14 +3,13 @@ uvicorn[standard]
sqlalchemy sqlalchemy
psycopg2-binary psycopg2-binary
alembic alembic
python-jose[cryptography] PyJWT
passlib[bcrypt] passlib[bcrypt]
bcrypt==4.0.1 bcrypt==4.0.1
boto3 boto3
apscheduler apscheduler
requests requests
pyyaml pyyaml
pySigma
toml toml
taxii2-client taxii2-client
python-multipart python-multipart
+5 -1
View File
@@ -8,7 +8,7 @@ line-length = 120
# I — isort (import ordering per PEP8 convention) # I — isort (import ordering per PEP8 convention)
# N — pep8-naming (class/function/variable naming conventions) # N — pep8-naming (class/function/variable naming conventions)
# ANN — flake8-annotations (type hint enforcement) # ANN — flake8-annotations (type hint enforcement)
select = ["E", "W", "F", "I", "N", "ANN"] select = ["E", "W", "F", "I", "N", "ANN", "D"]
ignore = [ ignore = [
# SQLAlchemy filter syntax requires `== True` / `== False` comparisons # SQLAlchemy filter syntax requires `== True` / `== False` comparisons
@@ -16,6 +16,10 @@ ignore = [
# ANN101/ANN102 (self/cls type annotations) removed from ruff — not needed # ANN101/ANN102 (self/cls type annotations) removed from ruff — not needed
] ]
[lint.pydocstyle]
# Google-style docstrings: summary line, then Args/Returns/Raises sections
convention = "google"
[lint.per-file-ignores] [lint.per-file-ignores]
# Tests use broad exception catching and unusual import patterns # Tests use broad exception catching and unusual import patterns
"tests/**" = ["E", "F", "N"] "tests/**" = ["E", "F", "N"]
+1 -1
View File
@@ -102,7 +102,7 @@ def test_logout_revokes_token(client, admin_user):
) )
assert out.status_code == 200 assert out.status_code == 200
from jose import jwt import jwt
from app.config import settings from app.config import settings
from app.infrastructure.redis_client import get_redis_blacklist from app.infrastructure.redis_client import get_redis_blacklist