diff --git a/gateway/platforms/api_server.py b/gateway/platforms/api_server.py index 2997e80de..c581e91da 100644 --- a/gateway/platforms/api_server.py +++ b/gateway/platforms/api_server.py @@ -207,6 +207,37 @@ def _openai_error(message: str, err_type: str = "invalid_request_error", param: } +# SECURITY FIX (V-013): Safe error handling to prevent info disclosure +def _handle_error_securely(exception: Exception, context: str = "") -> Dict[str, Any]: + """Handle errors securely - log full details, return generic message. + + Prevents information disclosure by not exposing internal error details + to API clients. Logs full stack trace internally for debugging. + + Args: + exception: The caught exception + context: Additional context about where the error occurred + + Returns: + OpenAI-style error response with generic message + """ + import traceback + + # Log full error details internally + error_id = str(uuid.uuid4())[:8] + logger.error( + f"Internal error [{error_id}] in {context}: {exception}\n" + f"{traceback.format_exc()}" + ) + + # Return generic error to client - no internal details + return _openai_error( + message=f"An internal error occurred. Reference: {error_id}", + err_type="internal_error", + code="internal_error" + ) + + if AIOHTTP_AVAILABLE: @web.middleware async def body_limit_middleware(request, handler): @@ -1084,7 +1115,8 @@ class APIServerAdapter(BasePlatformAdapter): jobs = self._cron_list(include_disabled=include_disabled) return web.json_response({"jobs": jobs}) except Exception as e: - return web.json_response({"error": str(e)}, status=500) + # SECURITY FIX (V-013): Use secure error handling + return web.json_response(_handle_error_securely(e, "list_jobs"), status=500) async def _handle_create_job(self, request: "web.Request") -> "web.Response": """POST /api/jobs — create a new cron job.""" @@ -1132,7 +1164,8 @@ class APIServerAdapter(BasePlatformAdapter): job = self._cron_create(**kwargs) return web.json_response({"job": job}) except Exception as e: - return web.json_response({"error": str(e)}, status=500) + # SECURITY FIX (V-013): Use secure error handling + return web.json_response(_handle_error_securely(e, "list_jobs"), status=500) async def _handle_get_job(self, request: "web.Request") -> "web.Response": """GET /api/jobs/{job_id} — get a single cron job.""" @@ -1151,7 +1184,8 @@ class APIServerAdapter(BasePlatformAdapter): return web.json_response({"error": "Job not found"}, status=404) return web.json_response({"job": job}) except Exception as e: - return web.json_response({"error": str(e)}, status=500) + # SECURITY FIX (V-013): Use secure error handling + return web.json_response(_handle_error_securely(e, "list_jobs"), status=500) async def _handle_update_job(self, request: "web.Request") -> "web.Response": """PATCH /api/jobs/{job_id} — update a cron job.""" @@ -1184,7 +1218,8 @@ class APIServerAdapter(BasePlatformAdapter): return web.json_response({"error": "Job not found"}, status=404) return web.json_response({"job": job}) except Exception as e: - return web.json_response({"error": str(e)}, status=500) + # SECURITY FIX (V-013): Use secure error handling + return web.json_response(_handle_error_securely(e, "list_jobs"), status=500) async def _handle_delete_job(self, request: "web.Request") -> "web.Response": """DELETE /api/jobs/{job_id} — delete a cron job.""" @@ -1203,7 +1238,8 @@ class APIServerAdapter(BasePlatformAdapter): return web.json_response({"error": "Job not found"}, status=404) return web.json_response({"ok": True}) except Exception as e: - return web.json_response({"error": str(e)}, status=500) + # SECURITY FIX (V-013): Use secure error handling + return web.json_response(_handle_error_securely(e, "list_jobs"), status=500) async def _handle_pause_job(self, request: "web.Request") -> "web.Response": """POST /api/jobs/{job_id}/pause — pause a cron job.""" @@ -1222,7 +1258,8 @@ class APIServerAdapter(BasePlatformAdapter): return web.json_response({"error": "Job not found"}, status=404) return web.json_response({"job": job}) except Exception as e: - return web.json_response({"error": str(e)}, status=500) + # SECURITY FIX (V-013): Use secure error handling + return web.json_response(_handle_error_securely(e, "list_jobs"), status=500) async def _handle_resume_job(self, request: "web.Request") -> "web.Response": """POST /api/jobs/{job_id}/resume — resume a paused cron job.""" @@ -1241,7 +1278,8 @@ class APIServerAdapter(BasePlatformAdapter): return web.json_response({"error": "Job not found"}, status=404) return web.json_response({"job": job}) except Exception as e: - return web.json_response({"error": str(e)}, status=500) + # SECURITY FIX (V-013): Use secure error handling + return web.json_response(_handle_error_securely(e, "list_jobs"), status=500) async def _handle_run_job(self, request: "web.Request") -> "web.Response": """POST /api/jobs/{job_id}/run — trigger immediate execution.""" @@ -1260,7 +1298,8 @@ class APIServerAdapter(BasePlatformAdapter): return web.json_response({"error": "Job not found"}, status=404) return web.json_response({"job": job}) except Exception as e: - return web.json_response({"error": str(e)}, status=500) + # SECURITY FIX (V-013): Use secure error handling + return web.json_response(_handle_error_securely(e, "list_jobs"), status=500) # ------------------------------------------------------------------ # Output extraction helper