Skip to content

[BREAKING] Python: DevUI: tighten default access controls and CORS posture#5740

Merged
moonbox3 merged 2 commits into
microsoft:mainfrom
moonbox3:devui-py-improvements
May 14, 2026
Merged

[BREAKING] Python: DevUI: tighten default access controls and CORS posture#5740
moonbox3 merged 2 commits into
microsoft:mainfrom
moonbox3:devui-py-improvements

Conversation

@moonbox3
Copy link
Copy Markdown
Contributor

Motivation and Context

The DevUI server's out-of-the-box defaults around CORS and auth were looser than most callers expect when running locally. This PR adjusts the defaults so a bare devui ./agents invocation matches the typical mental model (loopback, same-origin, gated), while keeping clean opt-outs for the rare cases that need the old behavior.

Description

DevServer now takes auth_enabled (default True) and auth_token (defaults to the DEVUI_AUTH_TOKEN env var, then an auto-generated value logged at startup). The Bearer-token middleware uses constant-time comparison.

CORS defaults to an empty allowlist on every host; callers wanting cross-origin pass cors_origins=[...] explicitly. Two streaming /v1/responses paths that previously set Access-Control-Allow-Origin themselves no longer do so — CORSMiddleware owns all CORS decisions.

A small Host-header allowlist middleware runs ahead of CORS on loopback binds, rejecting requests addressed to non-loopback names. /meta was moved out of the auth bypass list (it had been alongside /health and /).

serve() flips its default to auth_enabled=True and passes auth args through to DevServer directly, replacing the previous env-var indirection. The production-environment refusal (network bind + no explicit token) is preserved.

CLI: --auth opt-in is replaced with --no-auth opt-out; --auth-token is unchanged.

Eight new tests in test_server.py cover: streaming response headers, CORS default, auth-on-by-default, auth_enabled=False opt-out, valid token acceptance, /meta gating, Host-header allowlist, and serve() default. Two existing tests were updated to pass auth_enabled=False and a loopback base_url since they predate these defaults.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

Adjusts the default configuration of the DevUI server so the out-of-the-box
posture matches what most callers expect when running locally. Adds explicit
opt-outs for callers who need the previous behavior.

- DevServer gains auth_enabled and auth_token constructor params; auth is on by
  default. Auto-generates and logs a token when none provided.
- CORS default is an empty allowlist on every host. Callers wanting cross-origin
  pass cors_origins explicitly.
- Streaming /v1/responses no longer sets Access-Control-Allow-Origin directly;
  CORSMiddleware owns all CORS decisions.
- Loopback binds enforce a Host-header allowlist.
- /meta moved out of the auth bypass list (was alongside /health and /).
- serve() default flipped to auth_enabled=True; passes auth args through to
  DevServer instead of using env-var indirection.
- CLI: --auth opt-in replaced with --no-auth opt-out; --auth-token preserved.
- Tests cover the eight behaviors above in test_server.py.
Copilot AI review requested due to automatic review settings May 11, 2026 09:39
@moonbox3 moonbox3 changed the title Python: DevUI: tighten default access controls and CORS posture [BREAKING] Python: DevUI: tighten default access controls and CORS posture May 11, 2026
@moonbox3
Copy link
Copy Markdown
Contributor Author

moonbox3 commented May 11, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
TOTAL33815390688% 
report-only-changed-files is enabled. No files were changed during this commit :)

Python Unit Test Overview

Tests Skipped Failures Errors Time
6662 30 💤 0 ❌ 0 🔥 1m 48s ⏱️

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Tightens the Python DevUI server’s default security posture by enabling Bearer-token auth by default, removing permissive default CORS behavior, and adding a loopback Host-header allowlist to mitigate DNS rebinding—while providing explicit opt-outs for testing/embedded scenarios.

Changes:

  • DevServer now supports auth_enabled (default True) and auth_token (env var fallback, else auto-generated) with constant-time token comparison.
  • CORS defaults to an empty allowlist; streaming responses no longer hardcode Access-Control-Allow-Origin.
  • CLI and serve() defaults flip to auth-on-by-default (--no-auth opt-out) and tests are expanded/updated accordingly.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
python/packages/devui/agent_framework_devui/_server.py Implements new default auth, tighter CORS defaults, loopback Host-header allowlist, and removes hardcoded ACAO on streaming responses.
python/packages/devui/agent_framework_devui/init.py Updates serve() defaults to auth-enabled and adjusts token handling / safety checks.
python/packages/devui/agent_framework_devui/_cli.py Replaces --auth with --no-auth and wires auth args through to serve().
python/packages/devui/tests/devui/test_server.py Adds new tests for auth/CORS/host-header/streaming behavior and updates existing tests for new defaults.
python/packages/devui/tests/devui/test_ui_memory_regression.py Disables auth for the memory regression server fixture to keep the test focused.

Comment thread python/packages/devui/tests/devui/test_server.py
Comment thread python/packages/devui/tests/devui/test_server.py Outdated
Comment thread python/packages/devui/agent_framework_devui/_server.py Outdated
Comment thread python/packages/devui/agent_framework_devui/_server.py
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 4 | Confidence: 94%

✓ Correctness

The PR correctly tightens CORS defaults, auth posture, and adds DNS-rebinding protection. Two correctness issues found: (1) the /meta endpoint's auth_required response field still reads from the DEVUI_AUTH_TOKEN env var, but the PR removes the code in serve() that previously set that env var — so the field will report False even when auth is active; (2) the Host-header middleware's port-stripping logic (split(':', 1)[0]) breaks for IPv6 addresses like [::1], despite _LOOPBACK_HOSTS explicitly including them.

✓ Security Reliability

The PR correctly tightens CORS, auth defaults, and adds DNS-rebinding protection. However, the /meta endpoint's auth_required field (line 445 of _server.py) still reads os.getenv("DEVUI_AUTH_TOKEN") to determine whether auth is active. The old serve() wrote that env var (line 173 of __init__.py), but this PR removes that write. DevServer._resolve_auth_token() also reads but never writes the env var. As a result, /meta will report auth_required=False whenever the token is auto-generated or passed via the auth_token= parameter — exactly the default localhost path. This could mislead UI clients about the security posture.

✓ Test Coverage

The PR adds eight well-structured security tests covering the core posture changes (default auth, CORS tightening, host-header allowlist, CLI flag flip). However, test_streaming_response_does_not_hardcode_acao_header is vacuous because auth is enabled by default and the request carries no Bearer token, so auth middleware returns 401 before the streaming handler runs — the assertion passes trivially. Two additional behavioral paths lack coverage: rejection of an incorrect Bearer token (tests only cover missing token and valid token), and the DEVUI_AUTH_TOKEN env-var fallback in _resolve_auth_token (all tests either supply an explicit token or clear the env var).

✓ Design Approach

I found two design-level gaps in the security hardening. First, the new Host-header allowlist only recognizes four literal host strings, so valid loopback binds like 127.0.0.2 lose the DNS-rebinding protection this PR is trying to add. Second, auth configuration was moved onto DevServer, but /meta still reports auth_required from DEVUI_AUTH_TOKEN, so direct DevServer(auth_token=...) or serve(auth_token=...) instances can require auth while advertising that auth is off.


Automated review by moonbox3's agents

Comment thread python/packages/devui/agent_framework_devui/_server.py
- /meta now derives auth_required from self.auth_enabled instead of
  reading DEVUI_AUTH_TOKEN, so the auto-generated and explicit
  auth_token paths report correctly.
- Reorder middleware so the loopback Host-header allowlist is registered
  last; Starlette wraps later-added middleware around earlier-added ones,
  so the host check now runs outermost (before CORS/auth) as intended.
- Rework comments to describe the behavior rather than threat scenarios.
- Streaming-headers and CORS tests now construct the server with an
  explicit auth_token and send a Bearer header, so the assertions
  actually exercise the streaming/CORS path instead of short-circuiting
  in the auth middleware.
@moonbox3 moonbox3 added the devui DevUI-related items label May 11, 2026
@moonbox3 moonbox3 enabled auto-merge May 13, 2026 05:46
@moonbox3 moonbox3 added this pull request to the merge queue May 14, 2026
Merged via the queue into microsoft:main with commit fbccad0 May 14, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

devui DevUI-related items python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants