Skip to content

fix(transport): isHostAllowed mangles IPv6 Host headers #241

Description

@BryanFRD

Problem

isHostAllowed splits the Host header on : to strip the port, which corrupts bracketed IPv6 literals:

packages/mcp-core/src/transports/http.ts:36-41

const host = hostHeader.split(':')[0].toLowerCase();
return allowedHosts.some((allowed) => allowed.split(':')[0].toLowerCase() === host);

For Host: [::1]:3000 this yields '['. The allowlist entry [::1]:3000 is mangled the same way, so both sides reduce to '[' and happen to match; [::1] compared against [::1]:3000 does not. Either way the comparison is not doing what it reads as doing.

Why it matters

Any deployment reachable over IPv6 — a dual-stack cluster, ::1 in local testing — gets host checks decided by string accidents rather than by the configured allowlist. Requests are rejected with a 421 (http.ts:232-236) or accepted for the wrong reason, and the failure is hard to read because the mangled value never appears in a log.

Proposed approach

  • Parse the header with new URL(\http://${hostHeader}`)and compareurl.hostname`, which handles brackets and the missing-port case.
  • Normalise allowlist entries the same way at parse time (parseList) instead of at comparison time.
  • Cover [::1]:3000, [::1], localhost:3000, and localhost in packages/mcp-core/src/transports/__tests__/http.test.ts.

Acceptance criteria

  • An IPv6 Host header matches an IPv6 allowlist entry and only that entry.
  • A malformed Host header is rejected rather than silently reduced to a token that matches something.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low priority / somedaybugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions