fix open redirect, error leaking, unused imports, state validation

This commit is contained in:
CREDO23 2026-04-22 08:42:38 +02:00
parent e676ebfabe
commit 940889c291
6 changed files with 13 additions and 24 deletions

View file

@ -1,7 +1,5 @@
"""Shared auth helper for Discord agent tools (REST API, not gateway bot)."""
import logging
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
@ -9,8 +7,6 @@ from app.config import config
from app.db import SearchSourceConnector, SearchSourceConnectorType
from app.utils.oauth_security import TokenEncryption
logger = logging.getLogger(__name__)
DISCORD_API = "https://discord.com/api/v10"

View file

@ -1,14 +1,10 @@
"""Shared auth helper for Luma agent tools."""
import logging
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from app.db import SearchSourceConnector, SearchSourceConnectorType
logger = logging.getLogger(__name__)
LUMA_API = "https://public-api.luma.com/v1"

View file

@ -1,15 +1,9 @@
"""Shared auth helper for Teams agent tools (Microsoft Graph REST API)."""
import logging
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from app.config import config
from app.db import SearchSourceConnector, SearchSourceConnectorType
from app.utils.oauth_security import TokenEncryption
logger = logging.getLogger(__name__)
GRAPH_API = "https://graph.microsoft.com/v1.0"

View file

@ -96,7 +96,7 @@ router.include_router(logs_router)
router.include_router(circleback_webhook_router) # Circleback meeting webhooks
router.include_router(surfsense_docs_router) # Surfsense documentation for citations
router.include_router(notifications_router) # Notifications with Zero sync
router.include_router(mcp_oauth_router) # MCP OAuth 2.1 for Linear, Jira, ClickUp
router.include_router(mcp_oauth_router) # MCP OAuth 2.1 for Linear, Jira, ClickUp, Slack, Airtable
router.include_router(composio_router) # Composio OAuth and toolkit management
router.include_router(public_chat_router) # Public chat sharing and cloning
router.include_router(incentive_tasks_router) # Incentive tasks for earning free pages

View file

@ -182,7 +182,7 @@ async def connect_mcp_service(
except Exception as e:
logger.error("Failed to initiate %s MCP OAuth: %s", service, e, exc_info=True)
raise HTTPException(
status_code=500, detail=f"Failed to initiate {service} MCP OAuth: {e!s}",
status_code=500, detail=f"Failed to initiate {service} MCP OAuth.",
) from e
@ -221,6 +221,9 @@ async def mcp_oauth_callback(
space_id = data["space_id"]
svc_key = data.get("service", service)
if svc_key != service:
raise HTTPException(status_code=400, detail="State/path service mismatch")
from app.services.mcp_oauth.registry import get_service
svc = get_service(svc_key)
@ -315,7 +318,7 @@ async def mcp_oauth_callback(
svc.name, db_connector.id, user_id,
)
reauth_return_url = data.get("return_url")
if reauth_return_url and reauth_return_url.startswith("/"):
if reauth_return_url and reauth_return_url.startswith("/") and not reauth_return_url.startswith("//"):
return RedirectResponse(
url=f"{config.NEXT_FRONTEND_URL}{reauth_return_url}"
)
@ -347,7 +350,7 @@ async def mcp_oauth_callback(
except IntegrityError as e:
await session.rollback()
raise HTTPException(
status_code=409, detail=f"Database integrity error: {e!s}",
status_code=409, detail="A connector for this service already exists.",
) from e
_invalidate_cache(space_id)
@ -368,7 +371,7 @@ async def mcp_oauth_callback(
)
raise HTTPException(
status_code=500,
detail=f"Failed to complete {service} MCP OAuth: {e!s}",
detail=f"Failed to complete {service} MCP OAuth.",
) from e
@ -495,7 +498,7 @@ async def reauth_mcp_service(
)
raise HTTPException(
status_code=500,
detail=f"Failed to initiate {service} MCP re-auth: {e!s}",
detail=f"Failed to initiate {service} MCP re-auth.",
) from e

View file

@ -430,7 +430,7 @@ class OAuthConnectorRoute:
state_mgr = oauth._get_state_manager()
extra: dict[str, Any] = {"connector_id": connector_id}
if return_url and return_url.startswith("/"):
if return_url and return_url.startswith("/") and not return_url.startswith("//"):
extra["return_url"] = return_url
auth_params: dict[str, str] = {
@ -498,7 +498,7 @@ class OAuthConnectorRoute:
data = state_mgr.validate_state(state)
except Exception as e:
raise HTTPException(
status_code=400, detail=f"Invalid state parameter: {e!s}"
status_code=400, detail="Invalid or expired state parameter."
) from e
user_id = UUID(data["user_id"])
@ -552,7 +552,7 @@ class OAuthConnectorRoute:
db_connector.id,
user_id,
)
if reauth_return_url and reauth_return_url.startswith("/"):
if reauth_return_url and reauth_return_url.startswith("/") and not reauth_return_url.startswith("//"):
return RedirectResponse(
url=f"{config.NEXT_FRONTEND_URL}{reauth_return_url}"
)
@ -603,7 +603,7 @@ class OAuthConnectorRoute:
except IntegrityError as e:
await session.rollback()
raise HTTPException(
status_code=409, detail=f"Database integrity error: {e!s}"
status_code=409, detail="A connector for this service already exists."
) from e
logger.info(