Skip to content

Reject non-loopback Host in build_runner serve - #5092

Open
herdiyana256 wants to merge 1 commit into
dart-lang:masterfrom
herdiyana256:fix/serve-loopback-host-validation
Open

Reject non-loopback Host in build_runner serve#5092
herdiyana256 wants to merge 1 commit into
dart-lang:masterfrom
herdiyana256:fix/serve-loopback-host-validation

Conversation

@herdiyana256

Copy link
Copy Markdown

build_runner serve binds an HTTP server that streams build outputs, and by default binds to localhost so it is only reachable from the local machine. The request pipeline in ServeHandler.handlerFor did not validate the Host or Origin header on the asset serving path; only the live reload WebSocket had an origin check. A loopback HTTP server with no Host validation is reachable through DNS rebinding: a page the developer visits can rebind its own hostname to a loopback address and then read the served application and its source maps cross origin.

Concretely, with the server on localhost:8080, a page on attacker.example.com that rebinds its DNS to 127.0.0.1 can issue:

GET /main.dart.js HTTP/1.1
Host: attacker.example.com:8080

before: 200 OK          (serves the compiled app and its .js.map source maps)
after:  403 Forbidden

This validates the Host and Origin headers when the server is bound to a loopback interface, rejecting requests whose Host is absent or non loopback and requests carrying a non loopback Origin. It mirrors the daemon asset server, which already does this:

if (restrictToLoopback) {
  pipeline = pipeline.addMiddleware(_loopbackOnly);
}

serve_command.dart enables it whenever the bind hostname is loopback (the localhost default). When the user has explicitly bound a routable address such as --hostname 0.0.0.0 the restriction is skipped, so intentional network serving is unaffected.

Added tests in serve_handler_test.dart covering missing Host, non loopback Host, non loopback Origin, the allowed loopback case, and that the default unrestricted behavior is unchanged.

@davidmorgan
davidmorgan self-requested a review August 31, 2026 11:42

@davidmorgan davidmorgan 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.

In #5097 the _rejectCrossOrigin check addresses this problem but just for websockets; the version in this PR is better, as it only rejects if serving on loopback.

Please remove _rejectCrossOrigin from BuildUpdatesWebSocketHandler.

Comment thread build_runner/lib/src/commands/serve/server.dart Outdated
Comment thread build_runner/lib/src/commands/serve/server.dart Outdated
Comment thread build_runner/lib/src/commands/serve_command.dart Outdated
Comment thread build_runner/CHANGELOG.md Outdated
@davidmorgan

Copy link
Copy Markdown
Contributor

Failure on CI is because of stale generated files unrelated to this PR--please merge from master and uploaded to get the needed updates so CI can run. Or if you prefer to rebase onto master that's okay, it's a small PR.

Thanks!

Comment thread build_runner/lib/src/commands/serve/server.dart
@herdiyana256
herdiyana256 force-pushed the fix/serve-loopback-host-validation branch from f6f4aab to 9eac5f7 Compare August 31, 2026 16:18
@herdiyana256

Copy link
Copy Markdown
Author

Thanks for the review. Addressed all points and rebased onto master so CI has the up to date generated files:

  • Removed _rejectCrossOrigin from BuildUpdatesWebSocketHandler (and its test group); the loopback middleware now covers that path.
  • Moved the restriction after the logging middleware, so rejected requests are still logged.
  • Dropped the extra explanatory comments in server.dart and serve_command.dart.
  • Trimmed the CHANGELOG line after the comma.
  • restrictToLoopback is now decided from the actual bound address via server.address.isLoopback instead of parsing the hostname string. The middleware still validates the incoming Host/Origin header values, since those are the request-controlled strings being checked.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants