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:
+3
-3
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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"]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user