{"record":{"id":"b67ceff26968cc77","repo":"ory/hydra","slug":"ip-s-is-not-a-permitted-destination","errorCode":null,"errorMessage":"ip %s is not a permitted destination","messagePattern":"ip (.+?) is not a permitted destination","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/ipx/ip_validator.go","lineNumber":55,"sourceCode":"\treturn g.Wait()\n}\n\n// IsAssociatedIPAllowed returns nil for a domain (with NS lookup), IP, or IPv6 address if it\n// does not resolve to a private IP subnet. This is a first level of defense against\n// SSRF attacks by disallowing any domain or IP to resolve to a private network range.\n//\n// Please keep in mind that validations for domains is valid only when looking up.\n// A malicious actor could easily update the DSN record post validation to point\n// to an internal IP\nfunc IsAssociatedIPAllowed(ctx context.Context, ipOrHostnameOrURL string) error {\n\tipOrHostname := ipOrHostnameOrURL\n\tif parsed, err := url.ParseRequestURI(ipOrHostnameOrURL); err == nil {\n\t\tipOrHostname = parsed.Hostname()\n\t}\n\n\tif ip, err := netip.ParseAddr(ipOrHostname); err == nil {\n\t\tif !allowed(ip) {\n\t\t\treturn errors.Errorf(\"ip %s is not a permitted destination\", ip)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif addr, err := netip.ParseAddrPort(ipOrHostnameOrURL); err == nil {\n\t\tif !allowed(addr.Addr()) {\n\t\t\treturn errors.Errorf(\"ip %s is not a permitted destination\", addr.Addr())\n\t\t}\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithTimeoutCause(ctx, 2*time.Second, errors.New(\"DNS lookup timed out\"))\n\tdefer cancel()\n\tips, err := resolver.LookupNetIP(ctx, \"ip\", ipOrHostname)\n\tif err != nil {\n\t\tif dnsErr, ok := stderrors.AsType[*net.DNSError](err); ok {\n\t\t\t// Copy the `*net.DNSError` before masking `Server` to avoid a data\n\t\t\t// race: the DNS resolver uses `singleflight` to deduplicate","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/ipx/ip_validator.go#L37-L73","documentation":"IsAssociatedIPAllowed returns this error when the input parses as a bare IP address (netip.ParseAddr) and the allowed() predicate rejects it — i.e. the IP is not a permitted destination (typically because private/loopback/link-local ranges are disallowed as SSRF protection). Raised at ip_validator.go:55.","triggerScenarios":"Calling IsAssociatedIPAllowed (directly, via IsAssociatedIPAllowedWhenSet, or via AreAllAssociatedIPsAllowed) with a string like \"127.0.0.1\", \"10.0.0.5\", \"::1\", or any IPv4/IPv6 address that the configured allowed() check rejects.","commonSituations":"Configuring callbacks, webhooks, or endpoints pointing at localhost/internal cluster IPs; Docker/K8s environments where services resolve to private ranges; SSRF guardrails rejecting internal addresses in submitted URLs.","solutions":["Use a public, internet-routable IP or hostname instead of the rejected address","If the internal address is intentional, adjust the allowed() predicate / disallowed-range configuration to permit it","In dev, expose the internal service via a public tunnel or the host's external address"],"exampleFix":"// before\nerr := ipx.IsAssociatedIPAllowed(ctx, \"127.0.0.1:9000\") // loopback rejected\n// after\nerr := ipx.IsAssociatedIPAllowed(ctx, \"93.184.216.34:9000\") // public IP accepted","handlingStrategy":"validation","validationCode":"func isPublicIP(s string) bool {\n    ip, err := netip.ParseAddr(s)\n    if err != nil { return false }\n    return ip.IsGlobalUnicast() && !ip.IsPrivate() && !ip.IsLoopback() && !ip.IsLinkLocalUnicast()\n}","typeGuard":null,"tryCatchPattern":"if err := ipx.IsAssociatedIPAllowed(ctx, input); err != nil {\n    if strings.Contains(err.Error(), \"not a permitted destination\") {\n        return fmt.Errorf(\"endpoint %s is internal/blocked; use a public address\", input)\n    }\n    return err\n}","preventionTips":["Check addresses with netip.ParseAddr and IsPrivate/IsLoopback before submitting","Never configure localhost or cluster-internal IPs for external callbacks","Understand the SSRF rationale — internal destinations are blocked by design","For dev environments, use tunnels or the host's external address"],"tags":["network","ssrf","ip-validation","security"],"backgroundTag":"ip-not-permitted","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}