{"record":{"id":"e2c1c9c3ab495ee0","repo":"Tencent/WeKnora","slug":"connection-blocked-hostname-suffix-s-is-restrict","errorCode":null,"errorMessage":"connection blocked: hostname suffix %s is restricted","messagePattern":"connection blocked: hostname suffix (.+?) is restricted","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/utils/security.go","lineNumber":822,"sourceCode":"\t\t\tTimeout:   30 * time.Second,\n\t\t\tKeepAlive: 30 * time.Second,\n\t\t}\n\t\treturn dialer.DialContext(ctx, network, addr)\n\t}\n\tif restrictedPorts[port] {\n\t\treturn nil, fmt.Errorf(\"connection blocked: port %s is restricted\", port)\n\t}\n\n\t// Check if the host is a restricted hostname\n\thostLower := strings.ToLower(host)\n\tfor _, restricted := range restrictedHostnames {\n\t\tif hostLower == restricted {\n\t\t\treturn nil, fmt.Errorf(\"connection blocked: hostname %s is restricted\", host)\n\t\t}\n\t}\n\tfor _, suffix := range restrictedHostSuffixes {\n\t\tif strings.HasSuffix(hostLower, suffix) {\n\t\t\treturn nil, fmt.Errorf(\"connection blocked: hostname suffix %s is restricted\", suffix)\n\t\t}\n\t}\n\n\t// Resolve the hostname once, validate every answer, and then dial one of\n\t// those exact IPs. Dialing the original hostname here would make the\n\t// standard dialer resolve it a second time, leaving a DNS-rebinding window\n\t// between validation and connection establishment.\n\tips, err := net.DefaultResolver.LookupIPAddr(ctx, host)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"DNS resolution failed for %s: %w\", host, err)\n\t}\n\tif len(ips) == 0 {\n\t\treturn nil, fmt.Errorf(\"DNS resolution returned no addresses for %s\", host)\n\t}\n\n\t// Validate all resolved IPs\n\tfor _, ipAddr := range ips {\n\t\tif restricted, reason := isRestrictedIP(ipAddr.IP); restricted {","sourceCodeStart":804,"sourceCodeEnd":840,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/utils/security.go#L804-L840","documentation":"SSRFSafeDialContext blocked the connection because the lowercased hostname ends with one of the restrictedHostSuffixes. Suffix matching catches whole classes of forbidden names (e.g. \".internal\", \".local\", or metadata suffixes) without enumerating each host. This is a deliberate policy denial to stop lookalike subdomains from reaching restricted zones.","triggerScenarios":"Dialing through SSRFSafeDialContext / SSRFSafeGRPCDialer with a host whose name ends in a restricted suffix (e.g. \"myservice.internal\" when \".internal\" is restricted), on the non-whitelisted path. TestSSRFSafeDialContextRejectsRestrictedPortAtFinalSink drives the final-sink code path that performs this check.","commonSituations":"Internal service DNS zones that collide with the restricted suffix list; Kubernetes cluster-local names like \"svc.cluster.local\" hitting a \".local\" restriction; renaming services into a suffix the library treats as forbidden.","solutions":["Check restrictedHostSuffixes in internal/utils/security.go to see which suffix matched, then use a hostname outside the restricted zone.","If the suffix-restricted host is genuinely trusted, add the full host to the SSRF whitelist to bypass dial-time checks.","Reconfigure internal DNS/service names to an allowed zone (e.g. \".example.internal\" if only \".internal\" is blocked).","As a last resort, use a non-guarded dialer for that specific connection, accepting the loss of DNS-rebinding protection."],"exampleFix":"// before\nconn, err := utils.SSRFSafeDialContext(ctx, \"tcp\", \"billing.svc.cluster.local:443\") // \".local\" suffix restricted\n\n// after\nconn, err := utils.SSRFSafeDialContext(ctx, \"tcp\", \"billing.internal.example.com:443\") // or whitelist the host","handlingStrategy":"validation","validationCode":"host, _, _ := net.SplitHostPort(addr)\nh := strings.ToLower(host)\nfor _, suffix := range []string{\".internal\", \".local\" /* + restrictedHostSuffixes list */} {\n    if strings.HasSuffix(h, suffix) {\n        return fmt.Errorf(\"hostname %s uses restricted suffix %s; rename or whitelist\", host, suffix)\n    }\n}","typeGuard":null,"tryCatchPattern":"conn, err := utils.SSRFSafeDialContext(ctx, \"tcp\", addr)\nif err != nil && strings.Contains(err.Error(), \"hostname suffix\") {\n    return nil, fmt.Errorf(\"destination in restricted DNS zone: %w\", err)\n}","preventionTips":["Check restrictedHostSuffixes when planning internal DNS zones and service names.","Avoid \".local\" and similar commonly-restricted suffixes for services dialed by this client.","Whitelist full hostnames (not suffixes) for trusted destinations.","Run a config-lint step that dials all configured endpoints at deploy time."],"tags":["ssrf","restricted-hostname","dns-suffix","dial"],"backgroundTag":"host-blocked","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}