Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2025-09-12 - Prevent Error Details Leakage in Auth Decorators
**Vulnerability:** Exception details (`str(e)`) were returned in HTTP 401 responses inside the `login_required` decorator, leaking internal error messages and system internals to unauthenticated users.
**Learning:** Returning exception text in authentication failure handlers risks exposing sensitive database or internal application state to attackers.
**Prevention:** Always fail securely by returning generic error messages (e.g. `{"error": "Unauthorized"}`) on authentication failures.
4 changes: 2 additions & 2 deletions auth/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ def decorated_function(*args, **kwargs):
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
except Exception:
return jsonify({'error': 'Unauthorized'}), 401
return decorated_function


Expand Down
1 change: 1 addition & 0 deletions src/handoff/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Handoff module package."""
33 changes: 33 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import sys
from unittest.mock import MagicMock

# Mock external cache_db dependency 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

from flask import Flask, jsonify
from auth.utils import login_required

def test_login_required_unauthorized_does_not_leak_details(monkeypatch):
app = Flask(__name__)

@app.route("/protected")
@login_required
def protected_route():
return jsonify({"message": "success"})

# Force verify_jwt_in_request to raise an Exception with sensitive details
def mock_verify():
raise RuntimeError("Sensitive internal database stack trace or token parsing error")

monkeypatch.setattr("auth.utils.verify_jwt_in_request", mock_verify)

with app.test_client() as client:
response = client.get("/protected")
assert response.status_code == 401
data = response.get_json()
assert data == {"error": "Unauthorized"}
assert "details" not in data
assert "Sensitive internal" not in str(data)