fix: UploadFile ForwardRef + WebSocket 403 — root cause fixed

1. plugin_error_handler.py: Remove _UploadFile alias, import UploadFile directly
   so FastAPI can resolve ForwardRef('UploadFile') in the wrapper's namespace.
   Also import WebSocket for ForwardRef resolution.

2. main.py: Skip WebSocket routes in wrap_plugin_route — WebSocket endpoints
   must not be wrapped (different protocol, no JSONResponse on error)
This commit is contained in:
Agent Zero
2026-07-27 01:17:59 +02:00
parent 09cd1a5fe2
commit 7968630840
2 changed files with 8 additions and 2 deletions
+2 -1
View File
@@ -2,7 +2,8 @@
import logging import logging
import functools import functools
import inspect import inspect
from fastapi import UploadFile as _UploadFile # noqa: F401 — needed for ForwardRef resolution from fastapi import UploadFile # noqa: F401 — needed for ForwardRef resolution
from fastapi import WebSocket # noqa: F401 — needed for ForwardRef resolution of WebSocket params
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+6 -1
View File
@@ -11,6 +11,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import BaseHTTPMiddleware
from starlette.routing import WebSocketRoute
import importlib import importlib
import logging import logging
import os import os
@@ -434,8 +435,12 @@ def create_app() -> FastAPI:
try: try:
router_module = importlib.import_module(route_def.module) router_module = importlib.import_module(route_def.module)
router = getattr(router_module, route_def.router_attr) router = getattr(router_module, route_def.router_attr)
# Wrap each route handler with plugin error isolation # Wrap each HTTP route handler with plugin error isolation
# Skip WebSocket routes — the wrapper breaks WS parameter
# resolution and returns JSONResponse instead of WS close.
for route in router.routes: for route in router.routes:
if isinstance(route, WebSocketRoute):
continue
if hasattr(route, 'endpoint'): if hasattr(route, 'endpoint'):
route.endpoint = wrap_plugin_route(route.endpoint) route.endpoint = wrap_plugin_route(route.endpoint)
# Add active-plugin check as a router-level dependency # Add active-plugin check as a router-level dependency