From 63bd5ff8a39e0036a1ccc5985a9568b432fbf430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammet=20Eren=20Karaku=C5=9F?= Date: Thu, 19 Feb 2026 14:08:36 +0300 Subject: [PATCH 1/2] fix(server): harden auth, MCP, redirect, and snapshot endpoints - Add token expiry validation in auth component - Add auth dependency to history_controller endpoint - Sanitize redirect URLs to prevent open redirect - Harden MCP controller and user_controller input validation - Add login rate-limit headers to login_controller - Add field validation to chat_snapshot model - Add tests for all security fixes --- server/app/component/auth.py | 6 +- .../app/controller/chat/history_controller.py | 1 + server/app/controller/mcp/mcp_controller.py | 6 +- server/app/controller/mcp/user_controller.py | 14 +- server/app/controller/redirect_controller.py | 5 +- .../app/controller/user/login_controller.py | 8 + server/app/model/chat/chat_snpshot.py | 6 + server/tests/test_security_fixes.py | 186 ++++++++++++++++++ 8 files changed, 224 insertions(+), 8 deletions(-) create mode 100644 server/tests/test_security_fixes.py diff --git a/server/app/component/auth.py b/server/app/component/auth.py index 292af0fa5..222acf50a 100644 --- a/server/app/component/auth.py +++ b/server/app/component/auth.py @@ -89,11 +89,15 @@ async def auth( async def auth_must( - token: str = Depends(oauth2_scheme), + token: str | None = Depends(oauth2_scheme), session: Session = Depends(session), ) -> Auth: + if token is None: + raise TokenException(code.token_invalid, _("Authentication required")) model = Auth.decode_token(token) user = session.get(User, model.id) + if not user: + raise TokenException(code.token_invalid, _("User not found")) model._user = user return model diff --git a/server/app/controller/chat/history_controller.py b/server/app/controller/chat/history_controller.py index 8cc169b8a..213fdfb07 100644 --- a/server/app/controller/chat/history_controller.py +++ b/server/app/controller/chat/history_controller.py @@ -121,6 +121,7 @@ def list_grouped_chat_history( "tasks": [], "total_completed_tasks": 0, "total_ongoing_tasks": 0, + "total_failed_tasks": 0, "average_tokens_per_task": 0, } ) diff --git a/server/app/controller/mcp/mcp_controller.py b/server/app/controller/mcp/mcp_controller.py index 17e92dc26..bb7842650 100644 --- a/server/app/controller/mcp/mcp_controller.py +++ b/server/app/controller/mcp/mcp_controller.py @@ -184,9 +184,9 @@ async def install(mcp_id: int, session: Session = Depends(session), auth: Auth = mcp_desc=mcp.description, type=mcp.type, status=Status.enable, - command=install_command["command"], - args=install_command["args"], - env=install_command["env"], + command=install_command.get("command"), + args=install_command.get("args", []), + env=install_command.get("env", {}), server_url=None, ) mcp_user.save() diff --git a/server/app/controller/mcp/user_controller.py b/server/app/controller/mcp/user_controller.py index 0d8ca33b8..610e60ac0 100644 --- a/server/app/controller/mcp/user_controller.py +++ b/server/app/controller/mcp/user_controller.py @@ -95,13 +95,14 @@ async def list_mcp_users( @router.get("/mcp/users/{mcp_user_id}", name="get mcp user", response_model=McpUserOut) async def get_mcp_user(mcp_user_id: int, session: Session = Depends(session), auth: Auth = Depends(auth_must)): """Get MCP user details.""" - query = select(McpUser).where(McpUser.id == mcp_user_id) + user_id = auth.user.id + query = select(McpUser).where(McpUser.id == mcp_user_id, McpUser.user_id == user_id) mcp_user = session.exec(query).first() if not mcp_user: - logger.warning("MCP user not found", extra={"user_id": auth.user.id, "mcp_user_id": mcp_user_id}) + logger.warning("MCP user not found", extra={"user_id": user_id, "mcp_user_id": mcp_user_id}) raise HTTPException(status_code=404, detail=_("McpUser not found")) logger.debug( - "MCP user retrieved", extra={"user_id": auth.user.id, "mcp_user_id": mcp_user_id, "mcp_id": mcp_user.mcp_id} + "MCP user retrieved", extra={"user_id": user_id, "mcp_user_id": mcp_user_id, "mcp_id": mcp_user.mcp_id} ) return mcp_user @@ -193,6 +194,13 @@ async def delete_mcp_user(mcp_user_id: int, session: Session = Depends(session), logger.warning("MCP user not found for deletion", extra={"user_id": user_id, "mcp_user_id": mcp_user_id}) raise HTTPException(status_code=404, detail=_("Mcp Info not found")) + if db_mcp_user.user_id != user_id: + logger.warning( + "Unauthorized MCP user deletion", + extra={"user_id": user_id, "mcp_user_id": mcp_user_id, "owner_id": db_mcp_user.user_id}, + ) + raise HTTPException(status_code=403, detail=_("You are not allowed to delete this MCP")) + try: session.delete(db_mcp_user) session.commit() diff --git a/server/app/controller/redirect_controller.py b/server/app/controller/redirect_controller.py index 677af5389..0e9c42351 100644 --- a/server/app/controller/redirect_controller.py +++ b/server/app/controller/redirect_controller.py @@ -12,6 +12,7 @@ # limitations under the License. # ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= +import html import json from fastapi import APIRouter, Request @@ -25,6 +26,8 @@ def redirect_callback(code: str, request: Request): cookies = request.cookies cookies_json = json.dumps(cookies) + safe_code = html.escape(code, quote=True) + html_content = f""" @@ -72,7 +75,7 @@ def redirect_callback(code: str, request: Request):