* fix(server): guard the /fetch proxy against SSRF to link-local/metadata
The /fetch proxy forwarded any user-supplied http/https URL straight into
fetch(), so a malicious remote MCP server could advertise OAuth metadata
pointing at the cloud metadata endpoint (169.254.169.254) and have the proxy
exfiltrate instance credentials (CodeQL js/request-forgery, alert #53).
Add a targeted SSRF guard: resolve the target host and reject link-local /
cloud-metadata addresses (169.254.0.0/16, IPv6 link-local, AWS IPv6 IMDS).
Loopback and RFC1918/private LAN targets stay reachable, since connecting to
local and self-hosted MCP servers is a core Inspector use case. Redirects are
now followed manually so every hop is re-validated, closing the
redirect-to-internal bypass. Returns 403 on a blocked target.
Adds endpoint tests for a direct metadata target, a redirect into a blocked
address, and normal redirects between allowed hosts.
Resolves code-scanning alert #53. Part of #1706.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HiccCEh9mwCfVzE8qYcop1
* fix(server): block IPv4-mapped IPv6 form of metadata IP in /fetch guard
The IPv4-mapped IPv6 branch of isBlockedProxyAddress only matched the
dotted-decimal form (::ffff:169.254.169.254), but the WHATWG URL parser
serializes IPv4-mapped addresses in hex (::ffff:a9fe:a9fe), so
http://[::ffff:169.254.169.254]/ resolved past the guard and still reached
the cloud metadata service on dual-stack hosts — the exact vector this guard
targets.
Extract the embedded IPv4 from both the hex and dotted forms and re-check it.
Adds a regression test for the IPv4-mapped IPv6 metadata address.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HiccCEh9mwCfVzE8qYcop1
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>