safehttp is an SSRF-resistant wrapper around Go's net/http for outbound
requests whose URL is provided by an untrusted source.
Use it when your service fetches URLs from users, webhooks, OAuth metadata, imported documents, third-party API payloads, or any other source you do not fully control. It keeps those requests constrained to allowed public destinations and blocks loopback, RFC1918 networks, Kubernetes, cloud metadata, and other internal or special-use addresses.
NewClient() starts with a public-HTTPS-only configuration.
Requests must use HTTPS, target a public destination, and use port 443. URL
credentials, custom Request.Host, proxies, other schemes or ports, and
internal or special-use IP ranges are blocked.
Redirects are followed by default, but every redirect target is revalidated.
go get github.com/ayuhito/safehttpCreate one client and reuse it.
client, err := safehttp.NewClient(
safehttp.ClientTimeout(5*time.Second),
safehttp.MaxResponseBytes(10<<20),
safehttp.NoRedirects(),
)
if err != nil {
return err
}
resp, err := client.Get(rawURL)
if err != nil {
return err
}
defer resp.Body.Close()If the set of valid destinations is known, add a host allowlist.
client, err := safehttp.NewClient(
safehttp.AllowHosts(
"api.github.com",
"uploads.github.com",
"*.githubusercontent.com",
),
safehttp.AllowMethods(http.MethodGet, http.MethodHead),
safehttp.MaxRedirects(3),
safehttp.ClientTimeout(5*time.Second),
safehttp.MaxResponseBytes(8<<20),
)Use NewTransport to reuse an existing transport configuration.
base := http.DefaultTransport.(*http.Transport).Clone()
base.MaxIdleConnsPerHost = 32
rt, err := safehttp.NewTransport(base, safehttp.AllowHosts("api.github.com"))
if err != nil {
return err
}
client := &http.Client{Transport: rt}safehttp clones the transport before applying its settings, so the original
transport is not modified. Use safehttp.Dialer for custom dialer settings.
Transport settings that bypass safehttp's connection checks or disable TLS
certificate verification are rejected.
NewGuard exposes validation without constructing an http.Client.
guard, err := safehttp.NewGuard(safehttp.AllowHosts("api.github.com"))
if err != nil {
return err
}
if err := guard.CheckRequest(req); err != nil {
return err
}Use guard-only checks for preflight validation. They do not send the request;
use NewClient or NewTransport for the outbound HTTP path.
Constructors:
func NewClient(opts ...Option) (*http.Client, error)
func NewTransport(base *http.Transport, opts ...Option) (http.RoundTripper, error)
func NewGuard(opts ...Option) (*Guard, error)Options:
- destination policy:
AllowSchemes,AllowPorts,AllowHosts,AllowMethods - redirects:
MaxRedirects,NoRedirects - explicit opt-ins:
AllowCredentials,AllowCustomHostHeader - address policy:
AllowPrefixes,DenyPrefixes,AllowCIDRs,DenyCIDRs - transport/client limits:
Dialer,ClientTimeout,MaxResponseHeaderBytes,MaxResponseBytes
AllowPrefixes and AllowCIDRs opt in to additional address ranges, for
example private infrastructure or local tests. DenyPrefixes and DenyCIDRs
make the address policy stricter. Deny rules are evaluated before allow rules.
Create one client and reuse it. The added checks are negligible to performance and is safe to use concurrently.
go test -run '^$' -bench Benchmark -benchmemThis library was inspired by and borrows from the following projects: