Skip to content

fix: guard Streamable HTTP host headers#555

Open
zereight wants to merge 2 commits into
mainfrom
fix/dns-rebinding
Open

fix: guard Streamable HTTP host headers#555
zereight wants to merge 2 commits into
mainfrom
fix/dns-rebinding

Conversation

@zereight

@zereight zereight commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

  • reject disallowed Host/Origin headers on /mcp before JSON parsing
  • enable the MCP SDK DNS rebinding protection for Streamable HTTP transports
  • document MCP_ALLOWED_HOSTS / MCP_ALLOWED_ORIGINS and add regression tests

Addresses GHSA-vmp7-252j-cwp7.

Tests

  • npm run build
  • node --import tsx/esm --test test/streamable-http-dns-rebinding.test.ts

Note

Medium Risk
Touches the public /mcp request path; wrong allowlist config can block legitimate clients, but the change closes a security issue (GHSA-vmp7-252j-cwp7).

Overview
Adds DNS rebinding protection for Streamable HTTP by validating Host and Origin on /mcp before JSON parsing, returning 403 when headers are not on an allowlist.

Allowlists are built from loopback/HOST:PORT, MCP_SERVER_URL, and optional MCP_ALLOWED_HOSTS / MCP_ALLOWED_ORIGINS. The same lists are passed into StreamableHTTPServerTransport via the MCP SDK’s rebinding options. Startup now rejects invalid entries in those env vars.

README and the environment-variables reference document the new settings; test/streamable-http-dns-rebinding.test.ts covers forged headers and MCP_SERVER_URL acceptance.

Reviewed by Cursor Bugbot for commit 6adb624. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added MCP_ALLOWED_HOSTS and MCP_ALLOWED_ORIGINS environment variables to configure which hosts and browser origins can access the MCP endpoint.
    • MCP endpoint now validates Host and Origin headers on incoming requests.
  • Documentation

    • Updated environment variable documentation with new MCP configuration options and access requirements.
  • Tests

    • Added integration tests for MCP endpoint host and origin validation.

Walkthrough

Adds DNS rebinding protection for the Streamable HTTP /mcp endpoint by introducing an Express middleware (requireMcpHostAndOrigin) that enforces allowlists derived from MCP_ALLOWED_HOSTS, MCP_ALLOWED_ORIGINS, and MCP_SERVER_URL. The middleware is mounted at /mcp and its options are spread into StreamableHTTPServerTransport instances. Startup validation and documentation for the new environment variables are also added.

Changes

MCP DNS Rebinding Protection

Layer / File(s) Summary
Host/Origin allowlist middleware
index.ts
Introduces host/origin normalization helpers, constructs the MCP_DNS_REBINDING_PROTECTION allowlist object from MCP_ALLOWED_HOSTS/MCP_ALLOWED_ORIGINS/MCP_SERVER_URL, and implements requireMcpHostAndOrigin Express middleware that returns HTTP 403 for /mcp requests with non-allowlisted Host or Origin headers.
Startup validation and transport/route wiring
index.ts
Validates MCP_ALLOWED_HOSTS and MCP_ALLOWED_ORIGINS entries in validateConfiguration(), spreads MCP_DNS_REBINDING_PROTECTION into StreamableHTTPServerTransport constructor options for both init and non-init transport paths, and mounts requireMcpHostAndOrigin at /mcp in the Express app.
Integration tests and test script update
test/streamable-http-dns-rebinding.test.ts, package.json
New integration test file spawns the server, polls /health, and sends MCP JSON-RPC initialize requests with forged or valid Host/Origin headers to assert 403 rejections and 200 allowances. Updates the test:mock npm script to include the new test.
Documentation for new env vars
docs/configuration/environment-variables.md, README.md
Documents MCP_ALLOWED_HOSTS and MCP_ALLOWED_ORIGINS in the Streamable HTTP env-var reference with filtering rules and examples; adds them to the commonly referenced variables list; adds a combined entry in the REMOTE_AUTHORIZATION table; and expands the MCP_SERVER_URL row in the MCP OAuth Setup table to note its Host/Origin check role.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • zereight/gitlab-mcp#554: Both PRs update the same test:mock npm script command, while focusing on different transports (/mcp vs /sse).
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is overly broad and non-specific—'guard Streamable HTTP host headers' doesn't clearly convey that this is specifically DNS rebinding protection implementation. Consider a more specific title like 'add DNS rebinding protection for Streamable HTTP /mcp endpoint' to better reflect the main security improvement.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description is comprehensive, well-structured, and clearly explains the security changes, configuration options, and testing instructions related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dns-rebinding
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/dns-rebinding

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/configuration/environment-variables.md`:
- Around line 393-396: The documentation describing the Host and Origin header
rejection behavior uses confusing multiple negative statements that require
re-reading. Rewrite the paragraph starting with "Streamable HTTP rejects `/mcp`
requests..." to use positive language stating what IS allowed (local addresses,
MCP_SERVER_URL, and listed hosts/origins) rather than negative language stating
what is NOT allowed. Apply the same positive approach to the Origin header
description that follows, making it clear and direct about what values are
acceptable rather than what is rejected.

In `@test/streamable-http-dns-rebinding.test.ts`:
- Around line 57-70: Add a new test case that calls postMcp with a malformed
JSON body (instead of valid JSON) to verify that the Host/Origin rejection
occurs before JSON parsing is attempted. Create this additional test alongside
the existing forged Host/Origin checks to demonstrate that both valid and
invalid JSON payloads are rejected at the Host/Origin validation stage,
asserting that the same 403 error is returned regardless of payload validity.
- Around line 8-9: The HOST variable can contain IPv6 addresses like ::1, which
require square brackets when combined with a port number to create valid
HOST:PORT formats. Create a helper function that checks if the HOST contains
colons (indicating IPv6) and wraps it in square brackets before concatenating
with the port, then apply this function at all locations where HOST:port string
interpolation occurs (around lines 46-47, 80, and 115) to ensure proper URL and
Host header formatting regardless of whether HOST is an IPv4 or IPv6 address.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6f76082d-aeb5-4edb-968e-d4fa407847a1

📥 Commits

Reviewing files that changed from the base of the PR and between 40db3de and d4c3c7a.

📒 Files selected for processing (5)
  • README.md
  • docs/configuration/environment-variables.md
  • index.ts
  • package.json
  • test/streamable-http-dns-rebinding.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: test
  • GitHub Check: Cursor Bugbot
🧰 Additional context used
🪛 Betterleaks (1.5.0)
test/streamable-http-dns-rebinding.test.ts

[high] 10-10: Identified a GitLab Personal Access Token, risking unauthorized access to GitLab repositories and codebase exposure.

(gitlab-pat)

🔇 Additional comments (8)
README.md (1)

276-276: LGTM!

Also applies to: 327-328, 481-481

test/streamable-http-dns-rebinding.test.ts (1)

14-38: LGTM!

Also applies to: 40-55, 72-107, 124-146

package.json (1)

54-54: LGTM!

index.ts (5)

958-1051: LGTM!


1118-1131: LGTM!


12793-12801: LGTM!


12847-12848: LGTM!


13164-13166: LGTM!

Comment thread docs/configuration/environment-variables.md Outdated
Comment thread test/streamable-http-dns-rebinding.test.ts
Comment thread test/streamable-http-dns-rebinding.test.ts Outdated

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/configuration/environment-variables.md (1)

398-407: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider documenting that invalid entries in MCP_ALLOWED_HOSTS and MCP_ALLOWED_ORIGINS cause startup failure.

The PR objectives mention "startup validation to detect invalid entries," and the code (snippet 3) shows that the server validates comma-separated entries and rejects invalid hosts/origins at startup. However, the documentation does not mention this validation or its consequences. Users might otherwise assume that invalid entries are silently ignored.

Consider adding a note such as: "Invalid entries will cause startup failure with an error message indicating the problematic value."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/configuration/environment-variables.md` around lines 398 - 407, The
documentation sections for MCP_ALLOWED_HOSTS and MCP_ALLOWED_ORIGINS are missing
information about validation behavior. Add a note to both the MCP_ALLOWED_HOSTS
and MCP_ALLOWED_ORIGINS environment variable sections documenting that the
server validates entries at startup and that invalid entries (such as malformed
hosts or origins) will cause the server to fail startup with an error message
indicating the problematic value. This clarifies that invalid entries are not
silently ignored and helps users understand the validation consequences.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@docs/configuration/environment-variables.md`:
- Around line 398-407: The documentation sections for MCP_ALLOWED_HOSTS and
MCP_ALLOWED_ORIGINS are missing information about validation behavior. Add a
note to both the MCP_ALLOWED_HOSTS and MCP_ALLOWED_ORIGINS environment variable
sections documenting that the server validates entries at startup and that
invalid entries (such as malformed hosts or origins) will cause the server to
fail startup with an error message indicating the problematic value. This
clarifies that invalid entries are not silently ignored and helps users
understand the validation consequences.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 95c6890f-e6f5-4818-8165-4bce1bb4acfe

📥 Commits

Reviewing files that changed from the base of the PR and between d4c3c7a and 6adb624.

📒 Files selected for processing (2)
  • docs/configuration/environment-variables.md
  • test/streamable-http-dns-rebinding.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: coverage
  • GitHub Check: test
🔇 Additional comments (3)
test/streamable-http-dns-rebinding.test.ts (1)

10-18: LGTM!

Also applies to: 52-52, 65-66, 75-76, 85-85, 120-131

docs/configuration/environment-variables.md (2)

393-396: ✓ Grammatical clarity improved per previous feedback.

The documentation now uses positive language ("allows... when...") to describe the allowlisting behavior, directly addressing the prior review's suggestion to state what IS allowed rather than what is NOT allowed. The parallel structure for Host and Origin is clear.


393-407: The documentation accurately reflects the implementation. All three verification points are confirmed:

  1. Loopback addresses (127.0.0.1, localhost, ::1) are explicitly added to allowedHosts and allowedOrigins at startup (lines 1006-1009 of getMcpDnsRebindingProtection()).
  2. MCP_SERVER_URL is correctly parsed and added to both allowlists via addHost() and addOrigin() (lines 1012-1015).
  3. Normalization rules are properly implemented: formatHostWithPort() handles IPv6 bracket conversion and port formatting, while toAllowedMcpHost() and toAllowedMcpOrigin() normalize values through URL parsing and lowercasing before checking membership in the allowlists.

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.

1 participant