Skip to content

[Server] Accept plain http on *.localhost and 127.0.0.0/8 hosts in OAuth URLs - #620

Open
BrocksiNet wants to merge 1 commit into
modelcontextprotocol:mainfrom
BrocksiNet:fix/secure-url-localhost-subdomains
Open

BrocksiNet wants to merge 1 commit into
modelcontextprotocol:mainfrom
BrocksiNet:fix/secure-url-localhost-subdomains

Conversation

@BrocksiNet

Copy link
Copy Markdown
Contributor

Summary

  • SecureUrl treats localhost, any *.localhost (case-insensitive, one trailing dot allowed), the IPv4 loopback range 127.0.0.0/8 and ::1 as loopback, so plain http is accepted for them. Every other host still requires https, and lookalikes such as localhost.example.com, notlocalhost, localhostx, .localhost or 127.0.0.1.example.com stay rejected.
  • *.localhost is reserved for loopback by RFC 6761, section 6.3. The W3C Secure Contexts specification treats localhost, *.localhost, 127.0.0.0/8 and ::1 as potentially trustworthy, which is the set this change matches. The IPv4 check only applies to valid dotted-quad literals, so a hostname starting with 127. never matches.
  • This flips the existing http://127.0.0.2 rejection test: that address is loopback like 127.0.0.1, and I found no reason in [Server] Narrow authorization to the resource server role #532 for excluding it. Happy to drop the 127.0.0.0/8 part if you prefer to keep IPv4 at 127.0.0.1 only.
  • The MCP authorization spec says "All authorization server endpoints MUST be served over HTTPS" and "All redirect URIs MUST be either localhost or use HTTPS" (Communication Security). The SDK already relaxes https for loopback during development; this only makes that relaxation cover all loopback hosts.
  • The JwtTokenValidator::fromIssuer() docblock and docs/run/authorization.md list the accepted hosts. DnsRebindingProtectionMiddleware keeps its exact default allowedHosts (localhost, 127.0.0.1, [::1]): it is a configurable allow-list of Host headers with a different purpose, and widening a DNS rebinding default deserves its own discussion.

Motivation: Shopware dev environments serve each checkout on http://<name>.localhost:<port>. With SDK 0.9, ProtectedResourceMetadata throws there, so OAuth discovery breaks in local development: shopware/shopware#21862

Fixes #619

Test plan

  • New SecureUrlTest cases: accepted http://localhost, http://shop.localhost:8080, http://a.b.localhost, http://LOCALHOST., http://Shop.LocalHost.:8080/mcp, http://127.1.2.3 (plus the existing 127.0.0.1, [::1] cases); rejected http://localhost.example.com, http://shop.localhost.example.com, http://notlocalhost, http://localhostx, http://.localhost, http://10.0.0.1, http://127.0.0.1.example.com, http://[::ffff:127.0.0.1]
  • Without the fix the five new accepted cases fail
  • make tests (2963 tests, 8 skipped)
  • make phpstan
  • make cs
  • composer validate --strict

…n OAuth URLs

`SecureUrl` accepted plain http only for the exact hosts `localhost`,
`127.0.0.1` and `::1`. Names under the reserved `.localhost` TLD resolve to
loopback as well (RFC 6761, section 6.3), so `ProtectedResourceMetadata`,
`OidcDiscovery` and `JwtTokenValidator::fromIssuer()` threw for local
development setups served on hosts like `http://shop.localhost:8080`.

Treat `localhost`, any `*.localhost` (case-insensitive, trailing dot
allowed), the IPv4 loopback range `127.0.0.0/8` and `::1` as loopback,
matching the hosts the W3C Secure Contexts specification treats as
potentially trustworthy. Every other host still requires https.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T13:28:20.987936Z 45d77a8 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chr-hertel chr-hertel added Server Issues & PRs related to the Server component bug Something isn't working auth Issues and PRs related to Authentication / OAuth labels Oct 27, 2026
@chr-hertel
chr-hertel requested a balanced review from Copilot October 27, 2026 23:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

No active review issues are reported for the expanded loopback-host OAuth URL validation.

No open issues 🎉

What changed in this PR

Expands OAuth URL validation to support local development hosts such as shop.localhost, addressing #619 while keeping HTTPS required for non-loopback hosts.

Changes:

  • Accepts HTTP for *.localhost and valid IPv4 addresses in 127.0.0.0/8, with case-insensitive localhost matching and one optional trailing dot.
  • Adds tests for accepted loopback hosts and rejected lookalikes.
  • Documents the expanded loopback support; DNS rebinding defaults remain unchanged.
File Description
tests/​Unit/​Server/​Transport/​Http/​OAuth/​SecureUrlTest.php Adds loopback acceptance and lookalike rejection cases.
src/​Server/​Transport/​Http/​OAuth/​SecureUrl.php Extracts and expands loopback host detection.
src/​Server/​Transport/​Http/​OAuth/​JwtTokenValidator.php Updates the docblock to list accepted loopback hosts.
docs/​run/​authorization.md Documents expanded HTTP support for development hosts.
CHANGELOG.md Records the fix under version 0.9.1.

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth Issues and PRs related to Authentication / OAuth bug Something isn't working Server Issues & PRs related to the Server component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OAuth URLs on *.localhost hosts are rejected as insecure (SecureUrl loopback check too narrow)

3 participants