Skip to content

[Bug Fix] MCP: allow rubyui.com host in DNS-rebinding protection - #491

Open
djalmaaraujo wants to merge 1 commit into
mainfrom
fix/mcp-allowed-hosts
Open

[Bug Fix] MCP: allow rubyui.com host in DNS-rebinding protection#491
djalmaaraujo wants to merge 1 commit into
mainfrom
fix/mcp-allowed-hosts

Conversation

@djalmaaraujo

@djalmaaraujo djalmaaraujo commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #481

Root cause

The remote MCP endpoint at https://rubyui.com/mcp (and www.rubyui.com/mcp) rejected every JSON-RPC request with:

{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Forbidden: Invalid Host header"}}

RubyUI::MCP::RackApp#initialize (mcp/lib/ruby_ui/mcp/rack_app.rb) builds MCP::Server::Transports::StreamableHTTPTransport (from the third-party mcp gem) without an allowed_hosts: argument. That transport has built-in DNS-rebinding protection: it validates the incoming Host header against an allow list that defaults to loopback only (127.0.0.1, ::1, localhost). Since rubyui.com was never on that list, every real request got rejected by validate_host.

This is unrelated to Rails' own HostAuthorization middleware / config.hosts — that's commented out in docs/config/environments/production.rb and isn't the cause; the rejection happens entirely inside the mcp gem's transport.

Fix

RubyUI::MCP::RackApp now passes allowed_hosts: ["rubyui.com", "www.rubyui.com"] (plus the transport's own loopback defaults) when constructing the transport, extendable via a comma-separated RUBY_UI_MCP_ALLOWED_HOSTS env var — same pattern already used for RUBY_UI_MCP_REGISTRY in mcp/lib/ruby_ui/mcp/registry.rb.

allowed_origins: was not touched: the transport treats a request as same-origin (and thus allowed) whenever Origin matches Host, and non-browser MCP clients typically send no Origin header at all, so once Host is allow-listed the existing same-origin check already covers browser-based clients hitting rubyui.com/www.rubyui.com.

Second sub-problem (redirect)

The issue also reports https://rubyui.com/mcp 301-redirecting to https://www.rubyui.com/mcp, tripping up some MCP clients mid-handshake. I searched docs/config, docs/app/controllers, docs/config/routes.rb, and docs/Procfile and found no www-redirect logic, middleware, or infra config files (no _redirects, render.yaml, Cloudflare config, etc.) anywhere in the repo. config.force_ssl = true forces HTTPS only, not a www redirect. This redirect is happening at the Cloudflare/Render layer in front of the app, outside this repo — flagging as a known limitation/follow-up for whoever manages that infra config. This PR's fix does make it less harmful in the meantime: both rubyui.com and www.rubyui.com are now allow-listed, so a client that does follow the redirect will succeed on the second hop instead of failing on Host validation either way.

Test plan

  • Added mcp/test/rack_app_test.rb: asserts requests with Host: rubyui.com, Host: www.rubyui.com, and Host: localhost are not rejected with "Invalid Host header", asserts Host: evil.example.com is still rejected (DNS-rebinding protection intact), and covers the RUBY_UI_MCP_ALLOWED_HOSTS env override.
  • cd mcp && bundle exec rake test — 30 runs, 73 assertions, 0 failures, 0 errors, 0 skips.
  • cd mcp && bundle exec standardrb lib/ruby_ui/mcp/rack_app.rb test/rack_app_test.rb — clean, no offenses. (Note: bundle exec rake standard across the whole mcp/ tree currently reports pre-existing offenses in unrelated files — mcp/ has no committed Gemfile.lock, so bundle install resolved newer standard/rubocop versions than whatever last verified the tree; unrelated to this change.)
  • docs/ was not touched: /mcp routing itself doesn't change, so no SiteFiles/site_files:generate update is needed. docs depends on ruby_ui-mcp via path: "../mcp", so this fix ships with the next docs deploy without any version bump or gem republish.

Summary by cubic

Fixes “Invalid Host header” errors on the MCP endpoint by allow-listing production hosts while keeping DNS-rebinding protection intact. Requests to https://rubyui.com/mcp and https://www.rubyui.com/mcp now work.

  • Bug Fixes
    • Pass allowed_hosts to StreamableHTTPTransport with ["rubyui.com", "www.rubyui.com"], extendable via RUBY_UI_MCP_ALLOWED_HOSTS.
    • Preserve loopback defaults; non-allowed hosts still return 403.
    • Add tests covering production hosts, loopback, rejection of other hosts, and the env var override.

Written for commit 520d07d. Summary will update on new commits.

Review in cubic

StreamableHTTPTransport's DNS-rebinding protection only allows loopback
Host headers (127.0.0.1, ::1, localhost) by default, so every request
to https://rubyui.com/mcp was rejected with "Forbidden: Invalid Host
header". RackApp never passed allowed_hosts:, so production traffic
never had a chance to pass validation.

Pass allowed_hosts: with rubyui.com and www.rubyui.com (overridable via
RUBY_UI_MCP_ALLOWED_HOSTS), and add rack_app_test.rb covering both
allowed hosts, the loopback default, and that an unrelated host is
still rejected.

Fixes #481
@djalmaaraujo
djalmaaraujo requested a review from cirdes as a code owner July 27, 2026 20:12

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 2 files

Re-trigger cubic

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.

Remote MCP endpoint returns "Forbidden: Invalid Host header" for all requests

2 participants