safehttp

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 21, 2026 License: MIT Imports: 15 Imported by: 0

README

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:

Documentation

Overview

Package safehttp is an SSRF-resistant wrapper around Go's net/http for outbound requests to URLs provided by users or external systems.

Use it when a service fetches URLs from users, webhooks, OAuth metadata, imported documents, third-party API payloads, or any other source it does not fully control. safehttp keeps those requests constrained to allowed destinations and blocks loopback, private networks, 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 values, proxies, other schemes or ports, and private or special-use IP ranges are blocked. Redirects are followed by default, but every redirect target is revalidated.

NewGuard exposes URL and request validation without constructing an http.Client. Use guard-only checks for preflight validation; use NewClient or NewTransport for the outbound HTTP path.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrBlocked            = errors.New("safehttp: blocked request")
	ErrInvalidURL         = errors.New("safehttp: invalid url")
	ErrBlockedScheme      = errors.New("safehttp: blocked scheme")
	ErrBlockedHost        = errors.New("safehttp: blocked host")
	ErrBlockedPort        = errors.New("safehttp: blocked port")
	ErrBlockedMethod      = errors.New("safehttp: blocked method")
	ErrBlockedAddress     = errors.New("safehttp: blocked address")
	ErrBlockedNetwork     = errors.New("safehttp: blocked network")
	ErrBlockedRedirect    = errors.New("safehttp: blocked redirect")
	ErrBlockedCredentials = errors.New("safehttp: blocked credentials")
	ErrBlockedHostHeader  = errors.New("safehttp: blocked host header")
	ErrBlockedTransport   = errors.New("safehttp: blocked transport")
)

Functions

func NewClient

func NewClient(opts ...Option) (*http.Client, error)

NewClient builds an HTTP client with a guarded transport and redirect policy.

func NewTransport

func NewTransport(base *http.Transport, opts ...Option) (http.RoundTripper, error)

NewTransport clones base, installs guarded dialing, and returns a safe round tripper.

Types

type BlockError

type BlockError struct {
	// Reason is a short human-readable explanation of the decision.
	Reason string

	// URL is redacted before storage. It never includes URL credentials,
	// query strings, or fragments.
	URL string

	// Scheme, Host, Port, Method, and Addr identify the checked input when they
	// are known. Some errors only have one or two of these fields populated.
	Scheme string
	Host   string
	Port   uint16
	Method string
	Addr   netip.AddrPort

	// Rule is diagnostic detail for the matched policy rule, such as a denied
	// prefix, "private", "loopback", or a blocked network name. Use errors.Is
	// with the ErrBlocked* sentinels for stable control flow.
	Rule string
	// contains filtered or unexported fields
}

BlockError describes a request, redirect, address, or response blocked by safehttp.

func (*BlockError) Error

func (e *BlockError) Error() string

func (*BlockError) Is

func (e *BlockError) Is(target error) bool

func (*BlockError) Unwrap

func (e *BlockError) Unwrap() error

type Guard

type Guard struct {
	// contains filtered or unexported fields
}

Guard validates outbound HTTP requests, redirects, and dialed addresses.

func NewGuard

func NewGuard(opts ...Option) (*Guard, error)

NewGuard compiles options into a reusable concurrency-safe guard.

func (*Guard) CheckAddr

func (g *Guard) CheckAddr(network string, addr netip.AddrPort) error

CheckAddr validates the concrete network address selected for dialing.

This is the security boundary that matters after DNS resolution. A URL host like example.com may be syntactically fine, but the resolver can still return 127.0.0.1, 169.254.169.254, an RFC1918 address, or another special range. CheckAddr is used for both IP literal URLs and net.Dialer.ControlContext.

func (*Guard) CheckRedirect

func (g *Guard) CheckRedirect(req *http.Request, via []*http.Request) error

CheckRedirect validates a redirect target for http.Client.CheckRedirect.

func (*Guard) CheckRequest

func (g *Guard) CheckRequest(req *http.Request) error

CheckRequest validates an HTTP request before it is sent.

func (*Guard) CheckURL

func (g *Guard) CheckURL(u *url.URL) error

CheckURL validates a URL before it is used for an outbound request.

This check works only from the parsed url.URL fields; callers should parse raw strings before handing them to safehttp. DNS names are intentionally not resolved here. Resolution happens inside the transport, and the resulting address is checked later by CheckAddr through net.Dialer.ControlContext.

func (*Guard) ControlContext

func (g *Guard) ControlContext(_ context.Context, network, address string, _ syscall.RawConn) error

ControlContext implements net.Dialer.ControlContext.

type Option

type Option func(*options) error

Option changes safehttp construction.

func AllowCIDRs

func AllowCIDRs(cidrs ...string) Option

AllowCIDRs parses and permits destination CIDR ranges.

This is the string form of AllowPrefixes for callers that load policy from text or environment-specific configuration.

func AllowCredentials

func AllowCredentials() Option

AllowCredentials permits URL userinfo.

func AllowCustomHostHeader

func AllowCustomHostHeader() Option

AllowCustomHostHeader permits a Request.Host value that differs from the URL.

func AllowHosts

func AllowHosts(hosts ...string) Option

AllowHosts restricts requests to exact hosts or leading wildcard patterns.

func AllowMethods

func AllowMethods(methods ...string) Option

AllowMethods restricts requests to the provided HTTP methods.

func AllowPorts

func AllowPorts(ports ...uint16) Option

AllowPorts replaces the default allowed destination ports.

func AllowPrefixes

func AllowPrefixes(prefixes ...netip.Prefix) Option

AllowPrefixes permits destination IP prefixes that the default policy blocks.

Use it for tests, private infrastructure, or other non-public destinations. DenyPrefixes still wins when the same address is covered by both an allow rule and a deny rule.

func AllowSchemes

func AllowSchemes(schemes ...string) Option

AllowSchemes replaces the default allowed URL schemes.

func ClientTimeout

func ClientTimeout(timeout time.Duration) Option

ClientTimeout sets http.Client.Timeout on clients built by NewClient.

func DenyCIDRs

func DenyCIDRs(cidrs ...string) Option

DenyCIDRs parses and blocks additional destination CIDR ranges.

This is the string form of DenyPrefixes for callers that load policy from text or environment-specific configuration.

func DenyPrefixes

func DenyPrefixes(prefixes ...netip.Prefix) Option

DenyPrefixes blocks additional destination IP prefixes.

Deny rules are checked before allow rules. Use them to make the default public-destination policy stricter for an application.

func Dialer

func Dialer(dialer *net.Dialer) Option

Dialer uses a copy of dialer and installs safehttp's control hook on the copy.

Existing Control or ControlContext hooks are rejected because replacing them is how safehttp enforces the post-DNS address policy.

func MaxRedirects

func MaxRedirects(n int) Option

MaxRedirects sets how many redirects a client may follow.

func MaxResponseBytes

func MaxResponseBytes(n int64) Option

MaxResponseBytes caps the number of response body bytes a caller may read.

The limit is enforced while the caller reads the response body. safehttp does not buffer the body up front.

func MaxResponseHeaderBytes

func MaxResponseHeaderBytes(n int64) Option

MaxResponseHeaderBytes sets http.Transport.MaxResponseHeaderBytes.

func NoRedirects

func NoRedirects() Option

NoRedirects blocks every redirect.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL