Documentation
¶
Overview ¶
Package httpclient builds outbound HTTP clients for URLs that users control (webhooks, link previews, avatars by URL). Safe refuses to connect to loopback, private, link-local (cloud metadata), multicast and other internal addresses. The check runs on the resolved IP at connect time, so DNS rebinding and redirects to internal hosts are blocked too.
resp, err := httpclient.Safe(httpclient.Options{}).Get(userURL)
Options bound the timeout, redirects and body size and may allow extra prefixes; blocked destinations fail with ErrBlockedAddress. Get is the one-call form and Allowed the address check itself.
See https://github.com/AlexGalhardo/vukem/blob/main/docs/security.md.
Example ¶
package main
import (
"fmt"
"io"
"time"
"github.com/AlexGalhardo/vukem/httpclient"
)
func main() {
// Fetch a URL a user typed in (a webhook target, an avatar link).
// Internal destinations fail with ErrBlockedAddress.
client := httpclient.Safe(httpclient.Options{Timeout: 5 * time.Second})
resp, err := client.Get("https://example.com/avatar.png")
if err != nil {
return
}
defer resp.Body.Close()
avatar, err := io.ReadAll(resp.Body) // fails past Options.MaxBody
fmt.Println(len(avatar), err)
}
Output:
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrBlockedAddress = errors.New("httpclient: destination address is not allowed")
ErrBlockedAddress is returned when the destination is an internal address.
var ErrBodyTooLarge = errors.New("httpclient: response body too large")
ErrBodyTooLarge is returned by reads past Options.MaxBody.
var ErrNilTransport = errors.New("httpclient: Options.WrapTransport returned a nil transport")
ErrNilTransport is returned by every request of a client whose Options.WrapTransport returned nil.
Functions ¶
func Allowed ¶
Allowed reports whether ip may be reached with allow as extra prefixes.
Example ¶
package main
import (
"fmt"
"net/netip"
"github.com/AlexGalhardo/vukem/httpclient"
)
func main() {
for _, ip := range []string{"93.184.216.34", "127.0.0.1", "10.0.0.5", "169.254.169.254"} {
fmt.Println(ip, httpclient.Allowed(netip.MustParseAddr(ip), nil))
}
}
Output: 93.184.216.34 true 127.0.0.1 false 10.0.0.5 false 169.254.169.254 false
func Get ¶
Get is a convenience for Safe(Options{}).Get with a context.
Example ¶
package main
import (
"context"
"fmt"
"github.com/AlexGalhardo/vukem/httpclient"
)
func main() {
resp, err := httpclient.Get(context.Background(), "https://example.com/feed.xml")
if err != nil {
return
}
defer resp.Body.Close()
fmt.Println(resp.Status)
}
Output:
Types ¶
type Options ¶
type Options struct {
// Timeout bounds the whole request (default 10s).
Timeout time.Duration
// MaxBody bounds response bodies; reads past it fail (default 10 MiB).
MaxBody int64
// MaxRedirects (default 5).
MaxRedirects int
// Allow lists extra prefixes that may be reached (e.g. an internal
// service network). Use sparingly.
Allow []netip.Prefix
// WrapTransport, when set, wraps the client's transport (tracing,
// metrics; drivers/otel's Transport fits). It must delegate every
// request to the transport it is given: the address check runs in that
// transport's dialer, so a delegating wrapper cannot reach blocked
// addresses. A wrapper returning nil makes every request fail with
// ErrNilTransport (http.Client would otherwise use
// http.DefaultTransport, which has no address check).
WrapTransport func(http.RoundTripper) http.RoundTripper
}
Options tune Safe.