Repository navigation
Conversation
…k bind Host headers for IPv6 literals carry their own colons, so splitting on the first colon reduced both '[::1]' and '[::1]:8080' to '['. No allowlist member matches that, so every request a browser sends to http://[::1]:<port> was answered with 400 host_not_allowed, including on a bind to ::1 itself. Parse the host part with the brackets intact instead, and strip a trailing port only when the host is not an IPv6 literal.
lisry-1
temporarily deployed
to
github-app-auth
October 26, 2026 13:29 — with
GitHub Actions
Inactive
lisry-1
temporarily deployed
to
github-app-auth
October 26, 2026 13:29 — with
GitHub Actions
Inactive
Open
3 tasks done
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The narrow parsing fix addresses the reported regression with targeted tests and no identified blocking issues.
No open issues 🎉
What changed in this PR
Fixes #9709 by allowing DevUI’s loopback Host-header check to recognize IPv6 loopback addresses without splitting their internal colons.
Changes:
- Adds a hostname parser that handles bracketed IPv6 literals, optional ports, and bare IPv6 addresses.
- Uses the parser in the existing middleware without changing the host or authentication allowlists.
- Adds parser tests and IPv6 loopback regression coverage across three loopback bind values.
| File | Description |
|---|---|
python/packages/devui/tests/devui/test_server.py |
Tests hostname parsing, IPv6 loopback acceptance, and non-loopback rejection. |
python/packages/devui/agent_framework_devui/_server.py |
Adds IPv6-aware hostname parsing and integrates it into Host-header validation. |
Review effort: Balanced
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation & Context
On a loopback bind, DevUI only serves requests whose
Hostheader names a loopback address. The header was reduced to a host name withhost_header.split(":", 1)[0], which is correct for127.0.0.1:8080andlocalhost:8080but not for IPv6 literals: an IPv6 literal carries colons of its own, so[::1]and[::1]:8080both become[.[matches no member of_LOOPBACK_HOSTS({"127.0.0.1", "localhost", "[::1]", "::1"}), so every request was answered with400 host_not_allowed— including on a server bound to::1itself. A browser addressinghttp://[::1]:8080sends exactly that header, so DevUI is unusable over the IPv6 loopback.Reported in #9709.
Description & Review Guide
What are the major changes?
python/packages/devui/agent_framework_devui/_server.py:DevServer._hostname_from_host_header()static helper returns the host name part of aHostheader, lowercased and without a port:[is an IPv6 literal, so everything up to and including]is returned ([::1]:8080→[::1]);::1(which holds two) is left intact and still matches the allowlist;[falls through unchanged rather than being truncated.host_header_middlewareuses it in place ofsplit(":", 1)[0].lower().python/packages/devui/tests/devui/test_server.py:127.0.0.1,127.0.0.1:8080,localhost:8080,LOCALHOST:8080,[::1],[::1]:8080,[::FFFF:127.0.0.1]:8080,::1, empty)./healththroughTestClientwithHost: [::1]andHost: [::1]:8080on all three loopback binds (127.0.0.1,::1,[::1]), and asserts a non-loopbackHostis still rejected with 400.The allowlist itself is unchanged: non-loopback
Hostvalues, andHostvalues on public binds, behave exactly as before.What is the impact of these changes?
Requests to a loopback-bound DevUI addressed as
[::1]or[::1]:<port>are now served instead of rejected. Nothing else changes; the middleware still runs outermost, before CORS and auth.What do you want reviewers to focus on?
_AUTH_LOOPBACK_HOSTS({"127.0.0.1", "localhost"}) still omits::1and[::1], soDevServer(host="::1", auth_enabled=False)raises "authentication cannot be disabled for non-loopback hosts" even though::1is loopback. That is a separate, security-relevant widening of the no-auth allowlist, so I left it alone here. Say the word if you want it folded into this PR.Related Issue
Fixes #9709
This is submitted without prior maintainer sign-off on the issue. To be explicit about why I consider it under the trivial-change bar: the issue states the desired outcome ("requests with Host
[::1]or[::1]:<port>are accepted on a loopback bind... non-loopback hosts are still rejected"), there is no design choice or spec link involved, and the change cannot widen what is accepted beyond the loopback addresses the allowlist already names. If you would rather this waited for triage, say so and I will hold.Contribution Checklist
ruff checkandruff format --checkpass)AI Assistance
The reporter's reproduction script, run against this branch:
Before the fix, all six
[::1]rows returned 400. Confirmed by restoring the oldsplit(":", 1)[0]line and re-running:test_loopback_bind_accepts_ipv6_loopback_host_headerfails on all three binds withassert 400 == 200.Test run:
python/packages/devui/tests/devui/test_server.py62 passed.