From f725010fbe4d83cf9dc9853c0639364636d65285 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 22:35:34 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20Fix=20e?= =?UTF-8?q?rror=20detail=20leakage=20in=20login=5Frequired=20decorator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sanitize 401 response in auth/utils.py to avoid leaking sensitive internal exception details to unauthenticated callers. Log error internally instead. Co-authored-by: Pmaster-dev <293764797+Pmaster-dev@users.noreply.github.com> --- auth/utils.py | 19 +++++++++---- src/handoff/__init__.py | 0 tests/test_auth_utils.py | 59 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 src/handoff/__init__.py create mode 100644 tests/test_auth_utils.py diff --git a/auth/utils.py b/auth/utils.py index 4bded89..69f0b8d 100644 --- a/auth/utils.py +++ b/auth/utils.py @@ -1,14 +1,19 @@ -import bcrypt -import jwt +import logging import os import secrets from datetime import datetime, timedelta from functools import wraps -from flask import request, jsonify, g +from typing import Optional, Tuple + +import bcrypt +import jwt +from flask import g, jsonify, request from flask_jwt_extended import get_jwt_identity, verify_jwt_in_request + from cache_db.redis_client import redis_client -from cache_db.models import User, RefreshToken -from typing import Tuple, Optional +from cache_db.models import User + +logger = logging.getLogger(__name__) class PasswordUtils: @@ -129,7 +134,9 @@ def decorated_function(*args, **kwargs): g.user = user_data return f(*args, **kwargs) except Exception as e: - return jsonify({'error': 'Unauthorized', 'details': str(e)}), 401 + # Security: do not leak internal exception details to unauthenticated callers + logger.error("Authentication failed: %s", e) + return jsonify({'error': 'Unauthorized'}), 401 return decorated_function diff --git a/src/handoff/__init__.py b/src/handoff/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_auth_utils.py b/tests/test_auth_utils.py new file mode 100644 index 0000000..41632d8 --- /dev/null +++ b/tests/test_auth_utils.py @@ -0,0 +1,59 @@ +import sys +from unittest.mock import MagicMock + +# Mock cache_db module dependencies before importing auth.utils +cache_db_mock = MagicMock() +redis_client_mock = MagicMock() +models_mock = MagicMock() +cache_db_mock.redis_client = redis_client_mock +cache_db_mock.models = models_mock + +sys.modules["cache_db"] = cache_db_mock +sys.modules["cache_db.redis_client"] = redis_client_mock +sys.modules["cache_db.models"] = models_mock + +import pytest +from flask import Flask +from auth.utils import PasswordUtils, JWTUtils, login_required + + +def test_password_utils(): + hashed = PasswordUtils.hash_password("password123") + assert PasswordUtils.verify_password("password123", hashed) is True + assert PasswordUtils.verify_password("wrongpassword", hashed) is False + + with pytest.raises(ValueError): + PasswordUtils.hash_password("short") + + +def test_jwt_utils(): + access, refresh = JWTUtils.create_tokens("user1", "alice") + decoded_access = JWTUtils.decode_token(access) + assert decoded_access["user_id"] == "user1" + assert decoded_access["username"] == "alice" + assert decoded_access["type"] == "access" + + assert JWTUtils.decode_token("invalid.token.here") is None + + +def test_login_required_sanitizes_errors(monkeypatch): + app = Flask(__name__) + + @app.route("/protected") + @login_required + def protected(): + return "ok" + + client = app.test_client() + + # Force verify_jwt_in_request to raise an exception with sensitive internal details + def mock_verify(): + raise Exception("Sensitive DB connection error or stack info") + + monkeypatch.setattr("auth.utils.verify_jwt_in_request", mock_verify) + + response = client.get("/protected") + assert response.status_code == 401 + data = response.get_json() + assert data == {"error": "Unauthorized"} + assert "details" not in data From 05fe5a0b0d516a20564d61f4b029c5f106c4cad8 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 01:40:45 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20Fix=20e?= =?UTF-8?q?rror=20detail=20leakage=20in=20login=5Frequired=20decorator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sanitize 401 response in auth/utils.py to avoid leaking sensitive internal exception details to unauthenticated callers. Log error internally instead. Co-authored-by: Pmaster-dev <293764797+Pmaster-dev@users.noreply.github.com>