diff --git a/descope/common.py b/descope/common.py index abe045c24..0f7a9ae7a 100644 --- a/descope/common.py +++ b/descope/common.py @@ -8,7 +8,10 @@ DEFAULT_BASE_URL = DEFAULT_URL_PREFIX + "." + DEFAULT_DOMAIN # pragma: no cover DEFAULT_TIMEOUT_SECONDS = 60 -PHONE_REGEX = r"""^(?:(?:\(?(?:00|\+)([1-4]\d\d|[1-9]\d?)\)?)?[\-\.\ \\/]?)?((?:\(?\d{1,}\)?[\-\.\ \\/]?){0,})(?:[\-\.\ \\/]?(?:#|ext\.?|extension|x)[\-\.\ \\/]?(\d+))?$""" +# Simple phone validation to prevent ReDoS (catastrophic backtracking) +# Optional leading +, then digits, spaces, hyphens, parentheses, dots, # for extension +# Requires at least 4 consecutive digits, length 7-25, at most one leading + +PHONE_REGEX = r"""^(?=.*\d{4,})\+?[\d\s\-\(\)\.#xX]{6,24}$""" SESSION_COOKIE_NAME = "DS" REFRESH_SESSION_COOKIE_NAME = "DSR" diff --git a/descope/descope_client.py b/descope/descope_client.py index 610400901..3a07b01b2 100644 --- a/descope/descope_client.py +++ b/descope/descope_client.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import warnings from typing import Iterable import requests @@ -50,6 +51,17 @@ def __init__( ), ) + # Warn about TLS verification bypass + if skip_verify: + warnings.warn( + "⚠️ SECURITY WARNING: TLS certificate verification is DISABLED (skip_verify=True). " + "This makes your application vulnerable to man-in-the-middle attacks. " + "ONLY use this for local development with self-signed certificates. " + "NEVER use skip_verify=True in production environments.", + category=UserWarning, + stacklevel=2, + ) + # Auth Initialization auth_http_client = HTTPClient( project_id=project_id, diff --git a/descope/flask/__init__.py b/descope/flask/__init__.py index aab97141b..a0296e04f 100644 --- a/descope/flask/__init__.py +++ b/descope/flask/__init__.py @@ -34,7 +34,7 @@ def set_cookie_on_response(response: Response, token: dict, cookie_data: dict): expires=cookie_data.get("exp", expire_time), path=cookie_data.get("path", "/"), domain=cookie_domain, - secure=False, # True + secure=True, # Cookies must be sent over HTTPS only httponly=True, samesite="Strict", # "Strict", "Lax", "None" ) diff --git a/descope/jwt_common.py b/descope/jwt_common.py index 2183df5ea..500f047c7 100644 --- a/descope/jwt_common.py +++ b/descope/jwt_common.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from typing import Callable, Iterable, Optional import jwt @@ -71,9 +72,20 @@ def decode_token_unverified( ) -> dict: """Decode a JWT without verifying signature (used when no validator is provided). + WARNING: This function does NOT verify JWT signatures and should NEVER be used + for authentication or authorization decisions. It is only intended for testing + or scenarios where token validation happens elsewhere. + Audience verification is disabled by default since no key is provided. Returns an empty dict if decoding fails. """ + warnings.warn( + "decode_token_unverified() does not verify JWT signatures. " + "Do not use this for authentication or authorization. " + "Use proper token validation with signature verification instead.", + UserWarning, + stacklevel=2, + ) try: return jwt.decode( token, options={"verify_signature": False, "verify_aud": False} diff --git a/samples/otp_web_sample_app.py b/samples/otp_web_sample_app.py index e0ce2cddb..7f43f33f7 100644 --- a/samples/otp_web_sample_app.py +++ b/samples/otp_web_sample_app.py @@ -18,7 +18,9 @@ PROJECT_ID = "" -# init the DescopeClient +# SECURITY WARNING: skip_verify=True disables TLS certificate verification +# ONLY use this for local development with self-signed certificates +# NEVER use skip_verify=True in production - remove this parameter for production descope_client = DescopeClient(PROJECT_ID, skip_verify=True) diff --git a/samples/password_web_sample_app.py b/samples/password_web_sample_app.py index 13575c064..c3aaf44a3 100755 --- a/samples/password_web_sample_app.py +++ b/samples/password_web_sample_app.py @@ -14,7 +14,9 @@ PROJECT_ID = "" -# init the DescopeClient +# SECURITY WARNING: skip_verify=True disables TLS certificate verification +# ONLY use this for local development with self-signed certificates +# NEVER use skip_verify=True in production - remove this parameter for production descope_client = DescopeClient(PROJECT_ID, skip_verify=True) diff --git a/tests/test_jwt_common.py b/tests/test_jwt_common.py index a6b9c7add..9cfb47e6a 100644 --- a/tests/test_jwt_common.py +++ b/tests/test_jwt_common.py @@ -1,4 +1,5 @@ import unittest +import warnings from descope.jwt_common import ( COOKIE_DATA_NAME, @@ -58,7 +59,9 @@ def validator(token: str, audience=None): def test_decode_token_unverified_handles_garbage(self): # Invalid token strings should not raise and should return empty dict - assert decode_token_unverified("not-a-jwt") == {} + with warnings.catch_warnings(): + warnings.simplefilter("ignore", UserWarning) + assert decode_token_unverified("not-a-jwt") == {} if __name__ == "__main__":