From d7b8edc6406bd8c3a95ea5ebebbc1a256818de8a Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 22:49:33 +0000 Subject: [PATCH] Fix information disclosure vulnerability in login_required decorator Log authentication exceptions internally instead of exposing exception details (str(e)) in the HTTP 401 JSON response payload to callers. Co-authored-by: Pmaster-dev <293764797+Pmaster-dev@users.noreply.github.com> --- auth/utils.py | 19 ++++++++++------ src/handoff/__init__.py | 1 + tests/test_auth_security.py | 44 +++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 src/handoff/__init__.py create mode 100644 tests/test_auth_security.py diff --git a/auth/utils.py b/auth/utils.py index 4bded89..c8b1034 100644 --- a/auth/utils.py +++ b/auth/utils.py @@ -1,14 +1,17 @@ -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 + +logger = logging.getLogger(__name__) class PasswordUtils: @@ -115,7 +118,7 @@ def decorated_function(*args, **kwargs): try: verify_jwt_in_request() user_id = get_jwt_identity() - + # Try to get user from cache first user_data = redis_client.get_cached_user(user_id) if not user_data: @@ -124,12 +127,14 @@ def decorated_function(*args, **kwargs): return jsonify({'error': 'User not found or inactive'}), 401 user_data = user.to_dict() redis_client.cache_user(user_id, user_data) - + g.user_id = user_id g.user = user_data return f(*args, **kwargs) except Exception as e: - return jsonify({'error': 'Unauthorized', 'details': str(e)}), 401 + # SECURITY: Do not leak exception details or internal stack traces to clients. + logger.warning("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..25babc9 --- /dev/null +++ b/src/handoff/__init__.py @@ -0,0 +1 @@ +"""Handoff module initialization.""" diff --git a/tests/test_auth_security.py b/tests/test_auth_security.py new file mode 100644 index 0000000..8eed3f3 --- /dev/null +++ b/tests/test_auth_security.py @@ -0,0 +1,44 @@ +import sys +from unittest.mock import MagicMock + +# Mock external cache_db module before importing auth.utils +mock_cache_db = MagicMock() +sys.modules['cache_db'] = mock_cache_db +sys.modules['cache_db.redis_client'] = mock_cache_db.redis_client +sys.modules['cache_db.models'] = mock_cache_db.models + +import pytest +from flask import Flask +from auth.utils import login_required + + +@pytest.fixture +def app(): + app = Flask(__name__) + app.config['SECRET_KEY'] = 'test-secret' + + @app.route('/protected') + @login_required + def protected(): + return 'success' + + return app + + +def test_login_required_does_not_leak_error_details(app, monkeypatch): + """Test that authentication exceptions do not leak internal error details to clients.""" + # Simulate an internal exception during auth check (e.g. DB or Redis failure) + def mock_verify(): + raise RuntimeError("Internal database connection failed: postgres://user:secretpass@internal-host:5432/db") + + monkeypatch.setattr('auth.utils.verify_jwt_in_request', mock_verify) + + client = app.test_client() + response = client.get('/protected') + + assert response.status_code == 401 + data = response.get_json() + assert data == {'error': 'Unauthorized'} + assert 'details' not in data + assert 'secretpass' not in response.get_data(as_text=True) + assert 'postgres' not in response.get_data(as_text=True)