Skip to content

fix(validators): reject loopback/private/link-local hosts in IsValidRemoteURL#1470

Open
amitvijapur wants to merge 1 commit into
modelcontextprotocol:mainfrom
amitvijapur:fix/remote-url-loopback-validation
Open

fix(validators): reject loopback/private/link-local hosts in IsValidRemoteURL#1470
amitvijapur wants to merge 1 commit into
modelcontextprotocol:mainfrom
amitvijapur:fix/remote-url-loopback-validation

Conversation

@amitvijapur

Copy link
Copy Markdown

Fixes #1465

Cause

IsValidRemoteURL rejected only the literal strings localhost / 127.0.0.1 / *.localhost, so every other notation for a loopback or internal host passed validation: [::1], other 127.0.0.0/8 addresses (127.0.0.2), unspecified addresses (0.0.0.0, [::]), IPv4-mapped IPv6 forms ([::ffff:127.0.0.1]), and RFC1918/link-local addresses.

Change

Extracted the host check into isDisallowedRemoteHost: if the hostname parses as an IP (net.ParseIP — this includes bracketed IPv6 forms, which url.URL.Hostname() already strips, and IPv4-mapped forms), reject IsLoopback() || IsUnspecified() || IsPrivate() || IsLinkLocalUnicast(). Hostnames that don't parse as an IP keep the pre-existing localhost / *.localhost string checks, since resolving DNS names would require a network round trip during validation.

Tests

New table-driven TestIsValidRemoteURL_LoopbackAndPrivateAddresses in internal/validators/utils_test.go (20 subtests) covering every bypass listed in the issue plus sanity checks that public hosts still validate. go build ./..., go vet ./..., and gofmt all clean; golangci-lint run reports no new findings versus main on the touched package.

…emoteURL

IsValidRemoteURL only compared the hostname against the literal strings
"localhost", "127.0.0.1", and "*.localhost", so it missed every other
notation for an internal address: IPv6 loopback ([::1]), the rest of
127.0.0.0/8, unspecified addresses (0.0.0.0, [::]), IPv4-mapped IPv6
loopback ([::ffff:127.0.0.1]), and RFC1918/link-local ranges including
the 169.254.169.254 cloud metadata endpoint.

Parse the hostname with net.ParseIP and reject it if IsLoopback,
IsUnspecified, IsPrivate, or IsLinkLocalUnicast is true, so every IP
notation for these ranges is caught consistently. DNS names (which
can't be classified without a network round trip) still fall back to
the existing "localhost" / "*.localhost" string checks.

Fixes modelcontextprotocol#1465.
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.

IsValidRemoteURL only blocks literal localhost/127.0.0.1 — misses [::1], 127.0.0.0/8, 0.0.0.0, and private/link-local addresses

1 participant