googleapis/mcp-toolbox · error

redirect to blocked IP %s denied

Error message

redirect to blocked IP %s denied

What it means

During redirect handling, if the next hop's hostname is a literal IP address, the source checks it against the SSRF guard (guard.IsIPBlocked). Blocked IPs include private/loopback/link-local ranges, so this error stops a request from being redirected into an internal network. It is a deliberate SSRF protection rejection.

Source

Thrown at internal/sources/http/http.go:362

	if r, ok := resolver.(*net.Resolver); ok {
		dialer.Resolver = r
	}

	tr.DialContext = dialer.DialContext

	client := &http.Client{
		Timeout:   duration,
		Transport: tr,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			if len(via) >= 10 {
				return fmt.Errorf("stopped after 10 redirects")
			}

			hostname := req.URL.Hostname()
			if ip := net.ParseIP(hostname); ip != nil {
				if guard.IsIPBlocked(ip) {
					return fmt.Errorf("redirect to blocked IP %s denied", ip)
				}
				return nil
			}

			addrs, err := resolver.LookupHost(req.Context(), hostname)
			if err != nil {
				return fmt.Errorf("failed to resolve redirect host %s: %w", hostname, err)
			}

			for _, addr := range addrs {
				if ip := net.ParseIP(addr); ip != nil {
					if guard.IsIPBlocked(ip) {
						return fmt.Errorf("redirect host %s resolves to blocked IP %s", hostname, addr)
					}
				}
			}

			return nil

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Remove or fix the redirect target so the redirect chain stays on allowed public hosts.
  2. If the IP is legitimately needed, allowlist it in the guard's blocked-IP policy configuration.
  3. Verify no user-controlled input influences the initial URL that produces the redirect.

Example fix

// before: original URL redirects to http://169.254.169.254/latest/meta-data/
// after: point the tool at a public endpoint instead
toolArgs{URL: "https://example.com/api"}
Defensive patterns

Strategy: validation

Validate before calling

function isPublicIPv4(ip) {
  const parts = ip.split('.').map(Number);
  if (parts.length !== 4 || parts.some(p => isNaN(p))) return false;
  const [a, b] = parts;
  const blocked = a === 10 || a === 127 || (a === 172 && b >= 16 && b <= 31) ||
    (a === 192 && b === 168) || (a === 169 && b === 254) || a === 0 || a >= 224;
  return !blocked;
}
// reject URLs whose host is a blocked literal IP before calling the tool
const u = new URL(url); const host = u.hostname;
if (/^[0-9.]+$/.test(host) && !isPublicIPv4(host)) throw new Error('literal private/metadata IP not allowed');

Try / catch

try {
  const result = await callHttpTool(url);
} catch (err) {
  if (String(err).includes('redirect to blocked IP')) {
    console.error('Redirect target was a blocked/internal IP — remove SSRF-prone redirects from the server.');
  } else throw err;
}

Prevention

When it happens

Trigger: A 3xx response whose Location points to a URL with a literal IP host (e.g. http://169.254.169.254/ or http://127.0.0.1/) that guard.IsIPBlocked classifies as blocked.

Common situations: SSRF probing of cloud metadata endpoints via a redirect; a misconfigured server redirecting to localhost; user-supplied URL shorteners bouncing to internal addresses.

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/5e486112542aea3f. Report an issue: GitHub.