Skip to content

ayuhito/safehttp

Repository files navigation

safehttp

Go Reference

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.

Defaults

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.

Install

go get github.com/ayuhito/safehttp

Usage

Create 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),
)

Existing Transports

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.

Guard-Only Checks

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.

API Reference

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.

Performance

Create one client and reuse it. The added checks are negligible to performance and is safe to use concurrently.

go test -run '^$' -bench Benchmark -benchmem

Acknowledgements

This library was inspired by and borrows from the following projects:

About

A more secure SSRF-resistant HTTP client for Go.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Contributors

Languages