fix: address PR review — XSS escape, asyncio→anyio, URL rewrite dedup
- Escape HTML in _render_error to prevent XSS from exception messages - Replace asyncio.create_task/sleep with anyio task group and sleep, tying poll task lifetime to the app lifespan for proper cleanup - Extract rewrite_url_origin() utility to fix duplicated URL rewriting logic and replace urlparse._replace with stable urlunparse API - Add warning log for insecure HTTP redirect URIs - Add unit tests for validation, XSS escaping, route handlers, and URL rewriting (16 new tests in test_provision_routes.py) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
c21776948d
commit
777a09c806
+17
-13
@@ -1412,22 +1412,26 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
await anyio.sleep(3600) # Every hour
|
||||
|
||||
@asynccontextmanager
|
||||
async def _maybe_login_flow_cleanup():
|
||||
"""Start Login Flow cleanup task if enabled."""
|
||||
if settings.enable_login_flow:
|
||||
async with anyio.create_task_group() as tg:
|
||||
async def _maybe_login_flow_cleanup(app: Starlette):
|
||||
"""Start Login Flow cleanup task and provision poll task group."""
|
||||
async with anyio.create_task_group() as tg:
|
||||
if settings.enable_login_flow:
|
||||
tg.start_soon(_login_flow_cleanup_loop)
|
||||
yield
|
||||
tg.cancel_scope.cancel()
|
||||
else:
|
||||
# Share task group with provision routes for background polling
|
||||
for route in app.routes:
|
||||
if isinstance(route, Mount) and route.path == "/app":
|
||||
browser_app = cast(Starlette, route.app)
|
||||
browser_app.state.poll_task_group = tg
|
||||
break
|
||||
yield
|
||||
tg.cancel_scope.cancel()
|
||||
|
||||
@asynccontextmanager
|
||||
async def _mcp_session_with_login_flow():
|
||||
async def _mcp_session_with_login_flow(app: Starlette):
|
||||
"""Start MCP session manager with optional Login Flow cleanup."""
|
||||
async with AsyncExitStack() as stack:
|
||||
await stack.enter_async_context(mcp.session_manager.run())
|
||||
await stack.enter_async_context(_maybe_login_flow_cleanup())
|
||||
await stack.enter_async_context(_maybe_login_flow_cleanup(app))
|
||||
yield
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -1663,7 +1667,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
)
|
||||
|
||||
# Run MCP session manager and yield
|
||||
async with _mcp_session_with_login_flow():
|
||||
async with _mcp_session_with_login_flow(app):
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
@@ -1845,7 +1849,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
)
|
||||
|
||||
# Run MCP session manager and yield
|
||||
async with _mcp_session_with_login_flow():
|
||||
async with _mcp_session_with_login_flow(app):
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
@@ -1864,7 +1868,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
"To enable, set NEXTCLOUD_OIDC_CLIENT_ID and NEXTCLOUD_OIDC_CLIENT_SECRET."
|
||||
)
|
||||
# Just run MCP session manager without vector sync
|
||||
async with _mcp_session_with_login_flow():
|
||||
async with _mcp_session_with_login_flow(app):
|
||||
yield
|
||||
|
||||
else:
|
||||
@@ -1884,7 +1888,7 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
|
||||
logger.warning(
|
||||
"Vector sync enabled but TOKEN_ENCRYPTION_KEY not set"
|
||||
)
|
||||
async with _mcp_session_with_login_flow():
|
||||
async with _mcp_session_with_login_flow(app):
|
||||
yield
|
||||
|
||||
# Health check endpoints for Kubernetes probes
|
||||
|
||||
Reference in New Issue
Block a user