Skip to content

feat(http): Host header validation with :authority fallback and rejection logging#798

Merged
joshrotenberg merged 1 commit intomainfrom
feat/http-host-validation
May 7, 2026
Merged

feat(http): Host header validation with :authority fallback and rejection logging#798
joshrotenberg merged 1 commit intomainfrom
feat/http-host-validation

Conversation

@joshrotenberg
Copy link
Copy Markdown
Owner

Summary

Adds defense-in-depth Host header validation alongside our existing Origin checks. Origin protects browsers from cross-site rebinding; Host rejects requests whose Host header doesn't match the server's expected hostname, blocking direct DNS-rebinding attacks where a malicious site resolves its own domain to 127.0.0.1. All peer SDKs (TypeScript, Python, Go, Java, rmcp) ship both checks.

Bundles three rmcp PRs:

  • rust-sdk#764 -- the Host header allowlist itself.
  • rust-sdk#827 -- HTTP/2 :authority fallback for when middleware like axum::Router::nest strips the synthesized Host header.
  • rust-sdk#826 -- tracing::warn! on rejections so operators can alert via logs.

API additions

HttpTransport::new(router)
    .allowed_hosts(vec![\"api.example.com\".to_string()])
    // existing origin knobs
    .allowed_origins(vec![\"https://app.example.com\".to_string()]);

// or opt out entirely
HttpTransport::new(router).disable_host_validation();

UnixSocketTransport delegates the same two methods.

Behavior

  • Localhost host values (localhost / 127.0.0.1 / ::1, with any port, including bracketed IPv6) always pass.
  • Empty allowed_hosts means "don't enforce" -- existing deployments see no behavior change. Operators opt in by passing an allowlist.
  • Missing Host header falls back to request.uri().authority() so the HTTP/2 :authority pseudo-header works even when middleware strips Hyper's synthesized Host.
  • Rejections emit tracing::warn! with the offending value. Same logging added to the existing Origin rejection paths.
  • Wired into all three handlers (POST /, GET /, DELETE /). The latter two now extract from Request instead of just HeaderMap so they can read the URI authority.

Tests

9 new tests in transport::http::tests:

  • test_is_localhost_host_variants -- helper unit test (localhost, IPv6, ports)
  • test_host_validation_allows_localhost
  • test_host_validation_allows_configured_host
  • test_host_validation_rejects_unconfigured_host
  • test_host_validation_no_allowlist_accepts_any_host -- backward compat
  • test_disabled_host_validation_allows_any_with_allowlist -- opt-out
  • test_effective_host_prefers_header
  • test_effective_host_falls_back_to_authority
  • test_effective_host_returns_none_when_both_missing

682/682 lib tests pass.

Closes #796.

Test plan

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test -p tower-mcp --lib --all-features
  • cargo test -p tower-mcp --test '*' --all-features
  • cargo doc --no-deps --all-features

…tion logging

Adds defense-in-depth Host header validation alongside our existing
Origin checks. Origin protects browsers from cross-site rebinding;
Host rejects requests whose Host header doesn't match the server's
expected hostname, blocking direct DNS-rebinding attacks where a
malicious site resolves its own domain to 127.0.0.1.

API additions on HttpTransport (and delegated on UnixSocketTransport):

- disable_host_validation()
- allowed_hosts(Vec<String>)

Behavior:

- Localhost host values (localhost / 127.0.0.1 / ::1, with any port,
  including bracketed IPv6) always pass.
- Empty allowed_hosts means "don't enforce" -- existing deployments
  see no behavior change. Operators opt in by passing an allowlist.
- Missing Host header falls back to request.uri().authority() so the
  HTTP/2 :authority pseudo-header works even when middleware like
  axum::Router::nest strips Hyper's synthesized Host.
- Rejections emit tracing::warn! with the offending value, enabling
  log-based alerting for rebinding attempts. Same logging added to
  the existing Origin rejection paths.

Bundles three rmcp PRs (#764 host check, #826 logging, #827 :authority).

Closes #796
@joshrotenberg joshrotenberg merged commit 9107289 into main May 7, 2026
18 checks passed
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.

feat(http): Host header validation with HTTP/2 :authority fallback and rejection logging

1 participant