{"record":{"id":"2541003a64b41e75","repo":"googleapis/mcp-toolbox","slug":"redirect-host-s-resolves-to-blocked-ip-s","errorCode":null,"errorMessage":"redirect host %s resolves to blocked IP %s","messagePattern":"redirect host (.+?) resolves to blocked IP (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/sources/http/http.go","lineNumber":375,"sourceCode":"\t\t\t}\n\n\t\t\thostname := req.URL.Hostname()\n\t\t\tif ip := net.ParseIP(hostname); ip != nil {\n\t\t\t\tif guard.IsIPBlocked(ip) {\n\t\t\t\t\treturn fmt.Errorf(\"redirect to blocked IP %s denied\", ip)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\taddrs, err := resolver.LookupHost(req.Context(), hostname)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to resolve redirect host %s: %w\", hostname, err)\n\t\t\t}\n\n\t\t\tfor _, addr := range addrs {\n\t\t\t\tif ip := net.ParseIP(addr); ip != nil {\n\t\t\t\t\tif guard.IsIPBlocked(ip) {\n\t\t\t\t\t\treturn fmt.Errorf(\"redirect host %s resolves to blocked IP %s\", hostname, addr)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n\treturn client, nil\n}\n","sourceCodeStart":357,"sourceCodeEnd":385,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/internal/sources/http/http.go#L357-L385","documentation":"After resolving a redirect hostname, each returned address is checked with guard.IsIPBlocked; if any address is in a blocked (private/loopback/metadata) range, the redirect is denied with this error. This closes the DNS-rebinding/SSRF hole where a hostname resolves to an internal IP even though the URL looks public.","triggerScenarios":"A redirect whose hostname resolves (via LookupHost) to at least one IP classified as blocked by the guard, e.g. an internal DNS name or a hostname resolving to 10.x/127.x/169.254.x.","commonSituations":"Redirect to an internal service hostname that only resolves inside the VPC; split-horizon DNS returning private addresses; attacker-controlled DNS mapping a public name to 169.254.169.254.","solutions":["Change the redirect target to a publicly routable host that resolves only to public IPs.","If access to the internal host is intended, adjust the guard allowlist rather than bypassing the check.","Audit which service issues the redirect and remove internal-host references from public responses."],"exampleFix":"// before: Location: https://internal-svc.corp.local/api (resolves to 10.0.1.5)\n// after: expose via a public, allowlisted endpoint\nw.Header().Set(\"Location\", \"https://api.example.com/api\")","handlingStrategy":"validation","validationCode":"const host = new URL(redirectTarget).hostname;\nconst addrs = await require('dns').promises.lookup(host, { all: true });\nconst isPrivate = a => /^(10\\.|127\\.|192\\.168\\.|169\\.254\\.|172\\.(1[6-9]|2\\d|3[01])\\.)/.test(a.address);\nif (addrs.some(isPrivate)) console.warn(`redirect host ${host} resolves to a private IP; it will be denied by the SSRF guard`);","typeGuard":null,"tryCatchPattern":"try {\n  const result = await callHttpTool(url);\n} catch (err) {\n  if (/redirect host .* resolves to blocked IP/.test(String(err))) {\n    console.error('Redirect hostname resolves to an internal IP; point it at a public endpoint instead.');\n  } else throw err;\n}","preventionTips":["Keep public-facing redirects pointed at publicly routable hosts only.","Watch for split-horizon DNS names leaking into public redirect responses.","If internal access is intentional, request an explicit guard allowlist change rather than bypassing."],"tags":["ssrf","security","dns","redirect"],"backgroundTag":"ssrf-blocked-ip","analyzedSha":"8cc6e09de2ad7b8bffc77751799585a1401a48eb","analyzedAt":"2026-09-05T01:10:36.887Z","contentChangedAt":"2026-09-05T01:10:36.887Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}