Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions python/packages/devui/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ devui ./agents
devui --entities my_agent.py
```

## Security Posture

DevUI is a development-only sample app, not a production hosting surface. Authentication is enabled by default.
Unauthenticated mode is allowed only on `localhost` / `127.0.0.1`; `0.0.0.0`, LAN IPs, and hostnames require
`DEVUI_AUTH_TOKEN` or `--auth-token`.

## Import Path

```python
Expand Down
43 changes: 30 additions & 13 deletions python/packages/devui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ devui ./agents --port 8080
# → API: http://localhost:8080/v1/*
```

DevUI is auth-enabled by default. Localhost starts with a generated development token logged at startup; pass it as
`Authorization: Bearer <token>` for direct API calls.

When DevUI starts with no discovered entities, it displays a **sample entity gallery** with curated examples from the Agent Framework repository. You can download these samples, review them, and run them locally to get started quickly.

## Using MCP Tools
Expand Down Expand Up @@ -137,12 +140,14 @@ For convenience, DevUI provides an OpenAI Responses backend API. This means you
```bash
# Simple - use your entity name as the entity_id in metadata
curl -X POST http://localhost:8080/v1/responses \
-H "Authorization: Bearer <devui-token>" \
-H "Content-Type: application/json" \
-d @- << 'EOF'
{
"metadata": {"entity_id": "weather_agent"},
"input": "Hello world"
}
EOF
```

Or use the OpenAI Python SDK:
Expand All @@ -152,7 +157,7 @@ from openai import OpenAI

client = OpenAI(
base_url="http://localhost:8080/v1",
api_key="not-needed" # API key not required for local DevUI
api_key="<devui-token>"
)

response = client.responses.create(
Expand Down Expand Up @@ -201,6 +206,7 @@ DevUI provides an **OpenAI Proxy** feature for testing OpenAI models directly th

```bash
curl -X POST http://localhost:8080/v1/responses \
-H "Authorization: Bearer <devui-token>" \
-H "X-Proxy-Backend: openai" \
-d '{"model": "gpt-4.1-mini", "input": "Hello"}'
```
Expand All @@ -214,14 +220,14 @@ devui [directory] [options]

Options:
--port, -p Port (default: 8080)
--host Host (default: 127.0.0.1)
--host Host (default: 127.0.0.1; non-loopback hosts require auth)
--headless API only, no UI
--no-open Don't automatically open browser
--instrumentation Enable OpenTelemetry instrumentation
--reload Enable auto-reload
--mode developer|user (default: developer)
--auth Enable Bearer token authentication
--auth-token Custom authentication token
--no-auth Disable auth for loopback-only local development
--auth-token Custom authentication token (required for non-loopback hosts unless DEVUI_AUTH_TOKEN is set)
```

### UI Modes
Expand All @@ -233,8 +239,8 @@ Options:
# Development
devui ./agents

# Production (user-facing)
devui ./agents --mode user --auth
# Local-only no-auth development
devui ./agents --no-auth
```

## Key Endpoints
Expand Down Expand Up @@ -336,28 +342,39 @@ These custom extensions are clearly namespaced and can be safely ignored by stan

## Security

DevUI is designed as a **sample application for local development** and should not be exposed to untrusted networks without proper authentication.
DevUI is designed as a **sample application for local development** and is not intended for production use. For
production, or for features beyond this sample app, build a custom interface and API server using the Agent Framework SDK.

Auth is enabled by default. Unauthenticated mode is allowed only when DevUI is bound to `localhost` or `127.0.0.1`.
Network-reachable binds such as `0.0.0.0`, LAN IPs, and hostnames require Bearer token authentication with an explicit
token.

**For production deployments:**
**For shared development hosts:**

```bash
# User mode with authentication (recommended)
devui ./agents --mode user --auth --host 0.0.0.0
# Set a token explicitly before binding beyond loopback
DEVUI_AUTH_TOKEN="<secure-dev-token>" devui ./agents --mode user --host 0.0.0.0

# Or pass the token on the command line
devui ./agents --mode user --host 0.0.0.0 --auth-token "<secure-dev-token>"
```

This restricts developer APIs (reload, deployment, entity details) and requires Bearer token authentication.
Do not use `--no-auth` with `0.0.0.0`, LAN IPs, or hostnames. That configuration fails closed before startup.

**Security features:**

- User mode restricts developer-facing APIs
- Optional Bearer token authentication via `--auth`
- Bearer token authentication is enabled by default
- Unauthenticated mode is loopback-only (`localhost` / `127.0.0.1`)
- Non-loopback binds require `DEVUI_AUTH_TOKEN` or `--auth-token`
- Only loads entities from local directories or in-memory registration
- No remote code execution capabilities
- Binds to localhost (127.0.0.1) by default

**Best practices:**

- Use `--mode user --auth` for any deployment exposed to end users
- Do not use DevUI as a production deployment surface
- Use `--mode user` plus `DEVUI_AUTH_TOKEN` or `--auth-token` for shared development hosts
- Review all agent/workflow code before running
- Only load entities from trusted sources
- Use `.env` files for sensitive credentials (never commit them)
Expand Down
23 changes: 0 additions & 23 deletions python/packages/devui/agent_framework_devui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions python/packages/devui/agent_framework_devui/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@ def create_cli_parser() -> argparse.ArgumentParser:
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()}")
Expand Down
23 changes: 19 additions & 4 deletions python/packages/devui/agent_framework_devui/_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def __init__(
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
Expand All @@ -106,7 +106,7 @@ def __init__(
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()
Expand All @@ -118,8 +118,14 @@ def set_pending_entities(self, entities: list[Any]) -> None:
"""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.

Expand All @@ -131,16 +137,25 @@ def _loopback_allowed_hosts(self) -> frozenset[str] | None:
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:")
Expand Down
7 changes: 7 additions & 0 deletions python/packages/devui/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ devui

This launches the UI with all example agents/workflows at http://localhost:8080

DevUI is auth-enabled by default. Copy the generated token from startup logs and pass it as
`Authorization: Bearer <token>` for direct API calls. Use `--no-auth` only for loopback-only local testing.

## 5. What You'll See

- A web interface for testing agents interactively
Expand All @@ -74,6 +77,7 @@ You can also test via API calls:

```bash
curl -X POST http://localhost:8080/v1/responses \
-H "Authorization: Bearer <devui-token>" \
-H "Content-Type: application/json" \
-d '{
"model": "weather_agent",
Expand All @@ -86,13 +90,15 @@ curl -X POST http://localhost:8080/v1/responses \
```bash
# Create a conversation
curl -X POST http://localhost:8080/v1/conversations \
-H "Authorization: Bearer <devui-token>" \
-H "Content-Type: application/json" \
-d '{"metadata": {"agent_id": "weather_agent"}}'

# Returns: {"id": "conv_abc123", ...}

# Use conversation ID in requests
curl -X POST http://localhost:8080/v1/responses \
-H "Authorization: Bearer <devui-token>" \
-H "Content-Type: application/json" \
-d '{
"model": "weather_agent",
Expand All @@ -102,6 +108,7 @@ curl -X POST http://localhost:8080/v1/responses \

# Continue the conversation
curl -X POST http://localhost:8080/v1/responses \
-H "Authorization: Bearer <devui-token>" \
-H "Content-Type: application/json" \
-d '{
"model": "weather_agent",
Expand Down
Loading
Loading