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-09 11:50:40 -04:00
parent 8d1098f916
commit 975674d7f5
7 changed files with 362 additions and 3 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
@@ -61,11 +62,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),
})
@@ -250,9 +253,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"]),
@@ -287,4 +293,6 @@ def create_app() -> Starlette:
Route("/api/leases", api_leases, methods=["GET"]),
],
exception_handlers={405: method_not_allowed},
)
)
app.state.webui_bind_host = resolved_bind
return app