MCPcopy Create free account
hub / github.com/authorizerdev/authorizer / SafeHTTPClient

Function SafeHTTPClient

internal/validators/safe_http.go:22–89  ·  view source on GitHub ↗

SafeHTTPClient parses rawURL, resolves the host once, rejects any private, loopback, or otherwise non-routable IPs, and returns an *http.Client whose Transport.DialContext is pinned to dial the validated IP directly. This defeats SSRF DNS-rebinding TOCTOU because the http stack never re-resolves the

(ctx context.Context, rawURL string, timeout time.Duration)

Source from the content-addressed store, hash-verified

20// timeout applies to both the dial and the overall request. If timeout is 0,
21// a default of 30 seconds is used.
22func SafeHTTPClient(ctx context.Context, rawURL string, timeout time.Duration) (*http.Client, error) {
23 if timeout == 0 {
24 timeout = 30 * time.Second
25 }
26
27 u, err := url.Parse(rawURL)
28 if err != nil {
29 return nil, fmt.Errorf("malformed URL")
30 }
31 if u.Scheme != "http" && u.Scheme != "https" {
32 return nil, fmt.Errorf("only http and https schemes are allowed")
33 }
34 host := u.Hostname()
35 if host == "" {
36 return nil, fmt.Errorf("missing host")
37 }
38 port := u.Port()
39 if port == "" {
40 if u.Scheme == "https" {
41 port = "443"
42 } else {
43 port = "80"
44 }
45 }
46
47 var safeIP net.IP
48 if literal := net.ParseIP(host); literal != nil {
49 if isPrivateIP(literal) {
50 return nil, fmt.Errorf("requests to private/internal networks are not allowed")
51 }
52 safeIP = literal
53 } else {
54 ips, err := net.DefaultResolver.LookupIPAddr(ctx, host)
55 if err != nil {
56 return nil, fmt.Errorf("failed to resolve host: %w", err)
57 }
58 if len(ips) == 0 {
59 return nil, fmt.Errorf("no IP addresses resolved")
60 }
61 for _, ipa := range ips {
62 if isPrivateIP(ipa.IP) {
63 return nil, fmt.Errorf("requests to private/internal networks are not allowed")
64 }
65 if safeIP == nil {
66 safeIP = ipa.IP
67 }
68 }
69 }
70
71 dialAddr := net.JoinHostPort(safeIP.String(), port)
72 transport := &http.Transport{
73 DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
74 // Force the dial to the validated IP, ignoring whatever address
75 // the http machinery would otherwise re-resolve.
76 return (&net.Dialer{Timeout: timeout}).DialContext(ctx, network, dialAddr)
77 },
78 TLSClientConfig: &tls.Config{
79 ServerName: host,

Callers 3

TestEndpointMethod · 0.92
RegisterEventMethod · 0.92

Calls 2

isPrivateIPFunction · 0.85
StringMethod · 0.45

Tested by

no test coverage detected