feat(webui): auth and deployment boundary (#435)

Add bind-host assessment that refuses 0.0.0.0/:: without override,
warns on non-loopback binds, and documents internal-only MVP serving.
Health endpoint exposes deployment metadata; docs cover Access/VPN/WARP
and runtime env assumptions without embedding secrets in the client.

Closes #435
This commit is contained in:
2026-07-07 15:37:32 -04:00
parent ee8e9a0247
commit cb48e8a726
7 changed files with 361 additions and 4 deletions
+11 -3
View File
@@ -9,6 +9,7 @@ from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse, Response
from starlette.routing import Route
from webui.deployment_boundary import deployment_snapshot
from webui.layout import render_page
from webui.project_registry import find_project, load_registry, registry_to_dict
from webui.project_views import render_project_detail, render_projects_list
@@ -47,11 +48,13 @@ async def home(_request: Request) -> HTMLResponse:
async def health(_request: Request) -> JSONResponse:
bind_host = _request.app.state.webui_bind_host
return JSONResponse({
"status": "ok",
"service": "mcp-control-plane-webui",
"mode": "read-only-mvp",
"timestamp": datetime.now(timezone.utc).isoformat(),
"deployment": deployment_snapshot(bind_host=bind_host),
})
@@ -156,9 +159,12 @@ async def method_not_allowed(request: Request, _exc: Exception) -> Response:
return JSONResponse({"error": "not_found"}, status_code=404)
def create_app() -> Starlette:
def create_app(*, bind_host: str | None = None) -> Starlette:
"""Build the read-only MVP Starlette app."""
return Starlette(
import os
resolved_bind = bind_host or os.environ.get("WEBUI_HOST", "127.0.0.1")
app = Starlette(
debug=False,
routes=[
Route("/", home, methods=["GET"]),
@@ -177,4 +183,6 @@ def create_app() -> Starlette:
Route("/leases", leases, methods=["GET"]),
],
exception_handlers={405: method_not_allowed},
)
)
app.state.webui_bind_host = resolved_bind
return app