{"record":{"id":"48a89bc9b08026f6","repo":"JuliusBrussee/caveman","slug":"ssrf-dial-validated-addresses-for-q-w","errorCode":null,"errorMessage":"ssrf: dial validated addresses for %q: %w","messagePattern":"ssrf: dial validated addresses for %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/ssrf/ssrf.go","lineNumber":418,"sourceCode":"\t\t}\n\t\tchecked := make([]netip.Addr, 0, len(addrs))\n\t\tfor _, resolved := range addrs {\n\t\t\tresolved = resolved.WithZone(\"\").Unmap()\n\t\t\tif err := checkAddr(resolved, host, port, cfg); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tchecked = append(checked, resolved)\n\t\t}\n\n\t\tvar lastErr error\n\t\tfor _, resolved := range checked {\n\t\t\tconn, err := dial(ctx, network, net.JoinHostPort(resolved.String(), port))\n\t\t\tif err == nil {\n\t\t\t\treturn conn, nil\n\t\t\t}\n\t\t\tlastErr = err\n\t\t}\n\t\treturn nil, fmt.Errorf(\"ssrf: dial validated addresses for %q: %w\", host, lastErr)\n\t}\n}\n\n// NewHTTPClient returns an *http.Client whose Transport enforces the SSRF\n// policy at dial time.  The caller may set additional fields (Timeout, etc.)\n// on the returned client.\n//\n// Use this to create the gateway's upstream client so all outbound provider\n// requests are guarded even against DNS-rebinding attacks.\nfunc NewHTTPClient(cfg Config) *http.Client {\n\tt := http.DefaultTransport.(*http.Transport).Clone()\n\t// SSRF enforcement observes the address passed to DialContext. Go's\n\t// default transport may instead dial an HTTP(S)_PROXY address and leave the\n\t// proxy to connect to the request destination, which would move the guarded\n\t// boundary away from the host this client was built to protect. This package\n\t// has no destination-aware proxy contract, so protected clients are direct\n\t// by construction; callers that need a proxy must provide a separate,\n\t// explicitly validated client.","sourceCodeStart":400,"sourceCodeEnd":436,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/ssrf/ssrf.go#L400-L436","documentation":"Dial-time failure inside the SSRF DialContext: every address that passed range validation was attempted and the underlying dial errored for each; the last error is wrapped with the hostname via %w. Note the SSRF checks themselves succeeded — this is a genuine connectivity failure (refused, unreachable, TLS-timeout at TCP layer) on already-validated addresses.","triggerScenarios":"http.Client.Do on the guarded client where checkAddr passed for all resolved addresses but net.Dialer failed: connection refused (service down), no route/unreachable, firewall drop, or exhausted ephemeral ports.","commonSituations":"Upstream service stopped or crashed (connection refused); security-group/firewall blocking egress on the target port; pod restarted and old IP now unreachable; IPv6 address attempted first with broken v6 routing.","solutions":["Unwrap to the net.OpError: 'connection refused' means the target is down; 'i/o timeout'/'no route' means networking/firewall.","Check the service is listening on the expected port (ss -ltnp / kubectl get endpoints).","Fix egress rules or routing (especially IPv6) from the client environment.","Retry with backoff for transient restarts; connection refused right after a deploy usually self-heals."],"exampleFix":"// before\nresp, err := client.Get(u) // opaque 'dial validated addresses' error\n\n// after\nresp, err := client.Get(u)\nif err != nil {\n    var oe *net.OpError\n    if errors.As(err, &oe) {\n        switch {\n        case errors.Is(oe.Err, syscall.ECONNREFUSED): // service down\n        case oe.Timeout():                             // network/firewall\n        }\n    }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isDialFailure(err error) bool {\n    var oe *net.OpError\n    return errors.As(err, &oe)\n}","tryCatchPattern":"resp, err := client.Do(req)\nif err != nil {\n    var oe *net.OpError\n    if errors.As(err, &oe) {\n        if errors.Is(oe.Err, syscall.ECONNREFUSED) {\n            // service down: retry with backoff, then circuit-break\n        } else if oe.Timeout() {\n            // network/firewall: check egress before retrying\n        }\n    }\n}","preventionTips":["Read the wrapped net.OpError before retrying: refused = service down, timeout = network.","Health-check upstreams and use circuit breakers rather than blind retries.","After deploys, expect transient refusals while endpoints re-register — backoff, don't hammer."],"tags":["ssrf","dial","network","go"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}