Documentation
¶
Overview ¶
Package safefetch provides a production-grade safe HTTP fetch library with comprehensive SSRF protection, DNS Rebinding defense, redirect chain validation, response safety controls, and character encoding auto-detection and conversion.
Security Architecture ¶
safefetch implements a 4-layer defense-in-depth architecture:
- URL Pre-validation — scheme, port, hostname, credential, and backslash checks reject obviously malicious URLs before any network I/O.
- DialContext IP Verification — every resolved IP address is checked against built-in reserved/private ranges (RFC 1918, RFC 4193, CGN, link-local, loopback, TEST-NET, NAT64, 6to4, Teredo, etc.) and custom allow/block lists. This runs at connection time, defeating DNS Rebinding attacks.
- Dialer Address Re-validation — Dialer.Control re-checks the dial target address parameters before connect, and the established connection's remote IP is validated after connect as an additional safety net.
- Redirect Chain Validation — each redirect target is re-validated through all prior layers, preventing redirect-based SSRF bypasses.
Security Features ¶
- SSRF Protection: blocks connections to private, loopback, link-local, and other reserved IP ranges for both IPv4 and IPv6.
- DNS Rebinding Defense: IP checks at DialContext, Dialer.Control, and post-connect remote IP validation ensure the actually-connected IP is safe, not just the initially-resolved one.
- Redirect Chain Validation: every hop in a redirect chain is validated against the same security policies.
- Response Body Size Limiting: prevents memory exhaustion from oversized responses (default 10 MB).
- Content-Type Whitelisting: restricts acceptable MIME types (configurable via WithContentTypes; default allows text/html, application/xhtml+xml, application/json, text/plain, application/xml, text/xml, application/rss+xml, application/atom+xml). Responses without a Content-Type header are permitted.
- Encoding Detection & Conversion: auto-detects response character encoding and converts to UTF-8 when needed. If conversion fails, the original bytes are returned unchanged and EncodingConverted remains false.
Proxy Support ¶
Use WithProxy to route requests through an HTTP, HTTPS, or SOCKS5 proxy:
client, _ := safefetch.NewClient(
safefetch.WithProxy("http://proxy.corp.example:3128"),
)
Security considerations in proxy mode:
- Degraded layer: DNS resolution is performed by the proxy server, so the client cannot verify the destination site's actual IP address. The DialContext IP verification (layer 2) checks the proxy server's IP instead of the target's.
- Still enforced: URL pre-validation (scheme, port, dangerous hostnames), redirect chain validation, and IP verification of the proxy server itself.
- Proxy connection timeout is controlled by WithConnectTimeout.
- Only configure trusted proxy servers. The proxy effectively becomes part of your trust boundary.
When no proxy is configured, behavior is 100% backward-compatible.
Usage ¶
Create a Client with functional options, then call Fetch:
client, err := safefetch.NewClient(
safefetch.WithMaxBodySize(5 << 20), // 5 MB limit
safefetch.WithTimeout(15 * time.Second),
safefetch.WithContentTypes("text/html", "application/json"),
safefetch.WithAllowedPorts(80, 443),
)
if err != nil {
log.Fatal(err)
}
result, err := client.Fetch(ctx, "https://example.com/api/data")
if err != nil {
// Handle error — may be a sentinel error such as
// safefetch.ErrBlockedIP or safefetch.ErrContentType.
log.Fatal(err)
}
fmt.Println(result.StatusCode)
fmt.Println(string(result.Body))
Fetching through a proxy:
proxyClient, err := safefetch.NewClient(
safefetch.WithProxy("socks5://127.0.0.1:1080"),
safefetch.WithTimeout(30 * time.Second),
safefetch.WithConnectTimeout(5 * time.Second),
)
if err != nil {
log.Fatal(err)
}
result, err = proxyClient.Fetch(ctx, "https://example.com/api/data")
All security checks are enabled by default with safe defaults. Use the With* option functions to customize behavior for your use case.
Index ¶
- Variables
- type Client
- type Option
- func WithAllowCredentials() Option
- func WithAllowHTTPSDowngrade() Option
- func WithAllowedCIDR(cidrs ...string) Option
- func WithAllowedPorts(ports ...int) Option
- func WithBlockedCIDR(cidrs ...string) Option
- func WithConnectTimeout(d time.Duration) Option
- func WithContentTypes(types ...string) Option
- func WithHeaders(headers map[string]string) Option
- func WithMaxBodySize(n int64) Option
- func WithMaxRedirects(n int) Option
- func WithMaxResponseHeaderBytes(n int64) Option
- func WithProxy(proxyURL string) Option
- func WithResolver(r Resolver) Option
- func WithResponseHeaderTimeout(d time.Duration) Option
- func WithTLSHandshakeTimeout(d time.Duration) Option
- func WithTimeout(d time.Duration) Option
- func WithUserAgent(ua string) Option
- func WithoutEncodingConversion() Option
- type Resolver
- type Result
Constants ¶
This section is empty.
Variables ¶
var ( ErrBlockedIP = errors.New("safefetch: IP address is blocked") ErrBlockedScheme = errors.New("safefetch: URL scheme is blocked") ErrBlockedPort = errors.New("safefetch: port is blocked") ErrBlockedRedirect = errors.New("safefetch: redirect target is blocked") ErrTooManyRedirects = errors.New("safefetch: too many redirects") ErrBodyTooLarge = errors.New("safefetch: response body too large") ErrContentType = errors.New("safefetch: content type is not allowed") ErrTimeout = errors.New("safefetch: request timed out") ErrCanceled = errors.New("safefetch: request canceled") ErrBlockedHost = errors.New("safefetch: host is blocked") ErrBackslashInURL = errors.New("safefetch: backslash in URL is not allowed") ErrEmbeddedCredentials = errors.New("safefetch: embedded credentials in URL are not allowed") ErrInvalidOption = errors.New("safefetch: invalid option") )
Sentinel errors returned by safefetch when a request is denied or fails.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the main safefetch client. It holds resolved configuration, a pre-configured *http.Client, and precomputed CIDR prefix lists.
type Option ¶
type Option func(*options)
Option configures an options struct.
func WithAllowCredentials ¶
func WithAllowCredentials() Option
WithAllowCredentials permits URLs that contain embedded credentials.
func WithAllowHTTPSDowngrade ¶
func WithAllowHTTPSDowngrade() Option
WithAllowHTTPSDowngrade permits HTTPS to HTTP downgrade during redirects.
func WithAllowedCIDR ¶
WithAllowedCIDR sets custom allowed CIDR ranges (parsed later in NewClient).
func WithAllowedPorts ¶
WithAllowedPorts sets the whitelisted ports for outgoing requests.
func WithBlockedCIDR ¶
WithBlockedCIDR sets custom blocked CIDR ranges (parsed later in NewClient).
func WithConnectTimeout ¶
WithConnectTimeout sets the TCP connection timeout. NewClient returns ErrInvalidOption when d <= 0.
func WithContentTypes ¶
WithContentTypes sets the whitelisted Content-Type values. If called with one or more types, only responses matching those types are accepted. If called with no arguments (nil variadic), Content-Type filtering is disabled entirely and all response types are accepted. Expanding an explicit empty non-nil slice causes NewClient to return ErrInvalidOption.
func WithHeaders ¶
WithHeaders sets custom request headers.
func WithMaxBodySize ¶
WithMaxBodySize sets the maximum response body size in bytes. NewClient returns ErrInvalidOption when n <= 0.
func WithMaxRedirects ¶
WithMaxRedirects sets the maximum number of redirects to follow. NewClient returns ErrInvalidOption when n <= 0.
func WithMaxResponseHeaderBytes ¶
WithMaxResponseHeaderBytes sets the maximum size of response headers in bytes. NewClient returns ErrInvalidOption when n <= 0.
func WithProxy ¶
WithProxy sets the proxy URL for outgoing requests. Supported schemes are http, https, and socks5. The raw string is always stored; if url.Parse fails, the parsed URL is left nil and NewClient will return ErrInvalidOption during validation.
func WithResponseHeaderTimeout ¶
WithResponseHeaderTimeout sets the timeout for reading response headers. NewClient returns ErrInvalidOption when d <= 0.
func WithTLSHandshakeTimeout ¶
WithTLSHandshakeTimeout sets the TLS handshake timeout. NewClient returns ErrInvalidOption when d <= 0.
func WithTimeout ¶
WithTimeout sets the overall request timeout. NewClient returns ErrInvalidOption when d <= 0.
func WithUserAgent ¶
WithUserAgent sets the User-Agent header for requests.
func WithoutEncodingConversion ¶
func WithoutEncodingConversion() Option
WithoutEncodingConversion disables automatic encoding conversion to UTF-8.
type Resolver ¶
type Resolver interface {
LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)
}
Resolver is the interface used for DNS resolution. *net.Resolver satisfies this interface.
type Result ¶
type Result struct {
// StatusCode is the HTTP status code returned by the server.
StatusCode int
// Headers contains the HTTP response headers.
Headers http.Header
// Body is the raw response body as bytes.
Body []byte
// ContentType is the MIME type of the response (e.g. "text/html", "application/json").
ContentType string
// OriginalEncoding is the character encoding of the original response body
// before any conversion (e.g. "gbk", "shift_jis").
OriginalEncoding string
// EncodingConverted indicates whether the response body was converted
// from its original encoding to UTF-8.
EncodingConverted bool
// FinalURL is the URL after following all redirects.
FinalURL string
// RedirectCount is the number of redirects that were followed to reach the final URL.
RedirectCount int
}
Result holds the outcome of a safe URL fetch operation.