safehttp

package
v0.0.0-...-2769864 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

Documentation

Overview

Package safehttp provides an SSRF-guarded *http.Client.

Many URL-fetching paths here follow attacker-influenceable URLs — an LLM-emitted fetch_url target, an MCP server URL, an OAuth token endpoint discovered from a remote document — which unguarded can reach the cloud-metadata endpoint (169.254.169.254), loopback, or in-cluster services. Client()'s dialer resolves the host, refuses the request if ANY resolved IP is private/loopback/link-local, then dials the concrete vetted IP so the address checked is the address connected to (closing the DNS-rebinding gap). Redirects are re-validated per hop.

Stdlib-only by design: many packages import it, so it must stay dependency-free.

Index

Constants

This section is empty.

Variables

View Source
var BlockedCIDRs = mustParseCIDRs(
	"100.64.0.0/10",
	"192.0.0.0/24",
	"198.18.0.0/15",
	"240.0.0.0/4",
	"64:ff9b::/96",
	"2001:db8::/32",
	"100::/64",
)

BlockedCIDRs are the special-use ranges net.IP's own predicates do NOT cover. IsPrivate is RFC1918 (10/8, 172.16/12, 192.168/16) plus the IPv6 ULA and nothing else, so each range below was silently reachable through an SSRF-guarded client.

100.64.0.0/10 is the one with a live exposure rather than a theoretical one. GKE supports it as a pod/service CIDR, so on such a cluster it reaches ANY pod — precisely the in-cluster reach this guard exists to deny — and the per-session NetworkPolicies do not compensate, because their egress IPBlock is 0.0.0.0/0 with no Except clause. It is also the range a Tailscale tailnet uses, and this repo ships a tailscale-authkey identity flow whose sidecar shares the runner pod's network namespace.

Exported so an operator-facing knob can extend it: 100.64/10 is legitimately routable on some networks, and the list is the place to say so rather than a second predicate somewhere downstream.

Functions

func Client

func Client() *http.Client

Client returns an *http.Client whose transport dials only non-private, non-loopback, non-link-local destinations, re-validates redirects, and times out after 30s overall. The returned client is safe for concurrent use; callers may copy it and tighten Timeout if they need a shorter deadline.

func GuardHost

func GuardHost(host string) error

GuardHost reports whether host — typically (*url.URL).Hostname(), with no scheme or port — is a destination an SSRF-sensitive admission-time content check must refuse, WITHOUT performing a DNS lookup: an IP literal in a blocked range (see isBlockedIP — loopback, link-local unicast/multicast, which covers the 169.254.169.254 cloud-metadata endpoint, private RFC1918/ULA, and unspecified), "localhost", a metadata/internal-shaped hostname (the bare names "metadata" and "metadata.google.internal", or a ".internal"/".local" suffix — the same classification pkg/platform/oap/oci's registry guard uses for the same threat), or a Kubernetes in-cluster Service DNS name (a ".svc"/".svc.cluster.local"/ ".cluster.local" suffix, or a bare name with no dot at all — the cluster resolver's search-domain expansion tries those before ever treating a name as absolute).

This is a pre-dial, static content check, not a substitute for Client()'s guarded dialer: a hostname that looks public but is DNS-rebound to a blocked address at connect time is caught there, not here — the same division checkRedirect already draws for a redirect hop (an IP literal is checked immediately; a hostname is checked at connect time, so the address vetted is the address actually dialed).

func HardenServer

func HardenServer(s *http.Server)

HardenServer applies streaming-safe slow-loris timeouts to s: a ReadHeaderTimeout and an IdleTimeout. It deliberately leaves ReadTimeout and WriteTimeout untouched (0) so long-lived streaming/SSE responses and large request bodies are never truncated — a caller that serves only short request/response pairs may set those separately. Existing non-zero values are preserved, so a caller can opt into stricter limits before calling.

Types

This section is empty.

Jump to

Keyboard shortcuts

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