diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 65407a8..08cf793 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -21,3 +21,15 @@ Hardcoded credentials pose a critical risk because they provide an easy entry po **Prevention:** 1. Never commit secrets, API keys, or passwords into the source code repository. Always read sensitive configuration using environment variables (e.g., `os.getenv`). 2. Implement secure comparisons utilizing functions designed to prevent timing attacks, like `secrets.compare_digest()`, and properly encode inputs to prevent TypeErrors on non-ASCII characters. + +## 2024-05-20 - Exception Handling Information Leak in Admin Login + +**Vulnerability:** +The `main.py` application had a generic exception handler (`except Exception as e`) in the `/admin/login/` route that would catch all errors and re-raise them as an `HTTPException(status_code=401, detail=str(e))`. This incorrectly exposed internal details (like missing config errors raised as 500 `HTTPException`s from the auth module) directly to the client as strings within a 401 response, leaking internal stack details or configuration state. + +**Learning:** +When implementing exception handling in route definitions, generic catch-all handlers (`Exception`) should never reflect raw exception strings (`str(e)`) back to the client. This exposes potentially sensitive internal workings. Furthermore, explicitly throwing `HTTPException` in lower-level logic (like `auth.py`) is ineffective if a higher-level route blindly catches it as a generic `Exception` and changes the status code. + +**Prevention:** +1. Explicitly catch framework exceptions (like FastAPI's `HTTPException`) and re-raise them to preserve their intended status code and message. +2. Ensure generic `Exception` handlers return a sanitized, generic error message (e.g., "An unexpected error occurred") without exposing internal exception strings. diff --git a/main.py b/main.py index 977e1f2..001e4db 100644 --- a/main.py +++ b/main.py @@ -92,9 +92,12 @@ async def admin_login(login_request: models.LoginRequest, response: Response): sessions[session_id] = login_request.username # Map session ID to username response.set_cookie("session_id", session_id, httponly=True) return {"message": "Login successful"} + except HTTPException as he: + logging.error(f"Login failed for {login_request.username}: {str(he)}") + raise he except Exception as e: - logging.error(f"Login failed for {login_request.username}: {str(e)}") - raise HTTPException(status_code=401, detail=str(e)) # Unauthorized + logging.error(f"Unexpected login error for {login_request.username}: {str(e)}") + raise HTTPException(status_code=500, detail="An unexpected error occurred") # Admin logout route @app.post("/admin/logout/")