From cf8f70da2b771bce9d7fe0432c76652e8f73a3cc Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 10 Mar 2026 23:21:35 +0200 Subject: [PATCH] fix auth bypass on picker endpoint, async safety, and picker error handling - Add check_permission to drive-picker-token endpoint (IDOR fix) - Use get_composio_service singleton + asyncio.to_thread to avoid blocking the event loop - Sanitize error detail in 500 response to prevent internal info leakage - Dispose picker on unmount to prevent orphaned overlay - Surface error state on Google Picker Action.ERROR instead of silently closing --- .../routes/search_source_connectors_routes.py | 18 ++++++++++--- surfsense_web/hooks/use-google-picker.ts | 27 +++++++++++++------ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py index 64aeab431..800eb5629 100644 --- a/surfsense_backend/app/routes/search_source_connectors_routes.py +++ b/surfsense_backend/app/routes/search_source_connectors_routes.py @@ -52,7 +52,9 @@ from app.schemas import ( SearchSourceConnectorRead, SearchSourceConnectorUpdate, ) -from app.services.composio_service import ComposioService +import asyncio + +from app.services.composio_service import ComposioService, get_composio_service from app.services.notification_service import NotificationService from app.tasks.connector_indexers import ( index_airtable_records, @@ -3080,6 +3082,14 @@ async def get_drive_picker_token( if not connector: raise HTTPException(status_code=404, detail="Connector not found") + await check_permission( + session, + user, + connector.search_space_id, + Permission.CONNECTORS_READ.value, + "You don't have permission to access this connector", + ) + if connector.connector_type not in DRIVE_CONNECTOR_TYPES: raise HTTPException( status_code=400, @@ -3113,8 +3123,8 @@ async def get_drive_picker_token( status_code=400, detail="Composio connected account not found. Please reconnect.", ) - service = ComposioService() - access_token = service.get_access_token(composio_account_id) + service = get_composio_service() + access_token = await asyncio.to_thread(service.get_access_token, composio_account_id) return { "access_token": access_token, "client_id": config.GOOGLE_OAUTH_CLIENT_ID, @@ -3127,5 +3137,5 @@ async def get_drive_picker_token( logger.error(f"Failed to get Drive picker token: {e!s}", exc_info=True) raise HTTPException( status_code=500, - detail=f"Failed to retrieve access token: {e!s}", + detail="Failed to retrieve access token. Check server logs for details.", ) from e diff --git a/surfsense_web/hooks/use-google-picker.ts b/surfsense_web/hooks/use-google-picker.ts index 6aa019b0f..fa2a159b9 100644 --- a/surfsense_web/hooks/use-google-picker.ts +++ b/surfsense_web/hooks/use-google-picker.ts @@ -87,7 +87,14 @@ export function useGooglePicker({ connectorId, onPicked }: UseGooglePickerOption } }; window.addEventListener("keydown", onEscape); - return () => window.removeEventListener("keydown", onEscape); + return () => { + window.removeEventListener("keydown", onEscape); + if (pickerRef.current) { + pickerRef.current.dispose(); + pickerRef.current = null; + } + openingRef.current = false; + }; }, [closePicker]); const openPicker = useCallback(async () => { @@ -147,13 +154,17 @@ export function useGooglePicker({ connectorId, onPicked }: UseGooglePickerOption } } - if ( - action === google.picker.Action.PICKED || - action === google.picker.Action.CANCEL || - action === google.picker.Action.ERROR - ) { - closePicker(); - } + if (action === google.picker.Action.ERROR) { + setError("Google Drive encountered an error. Please try again."); + } + + if ( + action === google.picker.Action.PICKED || + action === google.picker.Action.CANCEL || + action === google.picker.Action.ERROR + ) { + closePicker(); + } }) .build();