Improvements for DevUI (#5840)

This commit is contained in:
Evan Mattson
2026-05-15 00:05:27 +09:00
committed by GitHub
Unverified
parent ae666a4887
commit 0e12640c70
7 changed files with 244 additions and 45 deletions
@@ -126,29 +126,6 @@ def serve(
if not isinstance(port, int) or not (1 <= port <= 65535):
raise ValueError(f"Invalid port: {port}. Must be integer between 1 and 65535")
# Security check: warn loudly when network-exposed without authentication.
if host not in ("127.0.0.1", "localhost") and not auth_enabled:
logger.warning("WARNING: Exposing DevUI to the network with --no-auth.")
logger.warning("Anyone on your network can read agent metadata and trigger requests.")
logger.warning("Drop --no-auth and DevUI will require Bearer tokens.")
# Refuse to auto-generate a token for network-exposed binds. Auto-generated tokens
# are fine for localhost convenience; for anything else, require an explicit token.
if auth_enabled and not auth_token:
import os
env_token = os.environ.get("DEVUI_AUTH_TOKEN")
if not env_token:
is_production = (
host not in ("127.0.0.1", "localhost")
or os.environ.get("CI") == "true"
or os.environ.get("KUBERNETES_SERVICE_HOST")
)
if is_production:
logger.error("Authentication required but no token provided.")
logger.error("Set DEVUI_AUTH_TOKEN env var or pass auth_token='...' to serve().")
raise ValueError("DEVUI_AUTH_TOKEN required when host is not localhost")
# Enable instrumentation if requested
if instrumentation_enabled:
from agent_framework.observability import enable_instrumentation
@@ -81,13 +81,15 @@ Examples:
parser.add_argument(
"--no-auth",
action="store_true",
help="Disable Bearer token authentication. DevUI is auth-enabled by default; use this to opt out.",
help=(
"Disable Bearer token authentication for loopback-only local development. Non-loopback hosts require auth."
),
)
parser.add_argument(
"--auth-token",
type=str,
help="Custom Bearer token. Auto-generated and logged at startup when omitted.",
help="Custom Bearer token. Required for non-loopback hosts when DEVUI_AUTH_TOKEN is not set.",
)
parser.add_argument("--version", action="version", version=f"Agent Framework DevUI {get_version()}")
@@ -89,7 +89,7 @@ class DevServer:
mode: Server mode - 'developer' (full access, verbose errors) or 'user' (restricted APIs, generic errors)
auth_enabled: Whether to require Bearer token auth on /v1/* endpoints. Defaults to True.
auth_token: Bearer token. If None and auth_enabled, falls back to the DEVUI_AUTH_TOKEN
environment variable, then to an auto-generated token (logged at startup).
environment variable. Loopback binds may use an auto-generated token logged at startup.
"""
self.entities_dir = entities_dir
self.port = port
@@ -106,7 +106,7 @@ class DevServer:
self.ui_enabled = ui_enabled
self.mode = mode
self.auth_enabled = auth_enabled
self.auth_token = self._resolve_auth_token(auth_enabled, auth_token)
self.auth_token = self._resolve_auth_token(host, auth_enabled, auth_token)
self.executor: AgentFrameworkExecutor | None = None
self.openai_executor: OpenAIExecutor | None = None
self.deployment_manager = DeploymentManager()
@@ -118,8 +118,14 @@ class DevServer:
"""Set in-memory entities to register on startup."""
self._pending_entities = entities
_AUTH_LOOPBACK_HOSTS = frozenset({"127.0.0.1", "localhost"})
_LOOPBACK_HOSTS = frozenset({"127.0.0.1", "localhost", "[::1]", "::1"})
@classmethod
def _is_auth_loopback_host(cls, host: str) -> bool:
"""Return True when unauthenticated DevUI may be limited to local loopback."""
return host.lower() in cls._AUTH_LOOPBACK_HOSTS
def _loopback_allowed_hosts(self) -> frozenset[str] | None:
"""Return the Host-header allowlist when bound to a loopback interface, else None.
@@ -131,16 +137,25 @@ class DevServer:
return None
return self._LOOPBACK_HOSTS
@staticmethod
def _resolve_auth_token(auth_enabled: bool, auth_token: str | None) -> str | None:
@classmethod
def _resolve_auth_token(cls, host: str, auth_enabled: bool, auth_token: str | None) -> str | None:
"""Resolve the active Bearer token. Returns None when auth is disabled."""
is_loopback = cls._is_auth_loopback_host(host)
if not auth_enabled:
if not is_loopback:
raise ValueError(
"DevUI authentication cannot be disabled for non-loopback hosts. "
"Bind to 127.0.0.1/localhost for no-auth local development, or enable auth and provide "
"DEVUI_AUTH_TOKEN or auth_token for network-reachable binds."
)
return None
if auth_token:
return auth_token
env_token = os.getenv("DEVUI_AUTH_TOKEN")
if env_token:
return env_token
if not is_loopback:
raise ValueError("DEVUI_AUTH_TOKEN or auth_token is required when DevUI is bound to a non-loopback host.")
generated = secrets.token_urlsafe(32)
logger.info("=" * 70)
logger.info("DevUI authentication enabled with auto-generated token:")