Help us shape the future of Go! Take the 2026 Go Developer Survey

netguard

package
v0.56.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package netguard is the one answer to "may filex dial this address?".

Two callers ask it, for the same reason: a URL an administrator or a plugin hands us must not become a probe of the server's own network. An install URL of http://169.254.169.254/… or http://10.0.0.5:9200/… is the SSRF shape, and so is a plugin's http_request to a public name that resolves to a private address. The check therefore runs on the DIALLED address, after DNS, and on every redirect hop — never on the string.

It lives in its own package so the plugin downloads (internal/plugin and internal/wasmplugin share DownloadClient) and the app-plugin outbound transport (internal/wasmplugin) cannot drift apart: one list of refused ranges, one guarded dialer, one redirect rule, one error.

Index

Constants

View Source
const MaxRedirects = 5

MaxRedirects is how many redirects a download follows.

Variables

View Source
var ErrDowngrade = errors.New("a redirect from https to plain http is not followed")

ErrDowngrade is a redirect from https:// to plain http://.

View Source
var ErrPrivateTarget = errors.New("resolves to a private or local address")

ErrPrivateTarget is what a guarded dial answers for a refused address. Callers match it with errors.Is to turn it into their own wording.

Functions

func Control added in v0.50.0

func Control(network, address string, _ syscall.RawConn) error

Control is a net.Dialer Control function that refuses a connection to an address Refused names. It runs on the RESOLVED address, right before the socket connects, so a name that resolves to something else between a check and the dial (DNS rebinding) is caught too. For clients that take a *net.Dialer and no dial function (an LDAP client).

func DialContext

func DialContext(dialer *net.Dialer) func(ctx context.Context, network, addr string) (net.Conn, error)

DialContext is a net.Dialer's DialContext wrapped in the Refused check: the name is resolved here, every answer is checked, and the connection is made to the address that passed — so nothing can re-resolve to something else between the check and the dial.

func Private

func Private(ip net.IP) bool

Private says whether an address is inside the private network — loopback, link-local, RFC 1918 or ULA. This is the WEAKER question, asked about a destination filex will send credentials to: plain http:// is tolerable there and nowhere else. It deliberately does not include multicast or the unspecified address, which are not destinations at all.

func Refused

func Refused(ip net.IP) bool

Refused reports whether a resolved address is one an outbound request may never reach: loopback, link-local (v4 169.254/16 and v6 fe80::/10, which is where cloud metadata lives), the RFC 1918 / ULA private ranges, multicast and the unspecified address.

A nil IP counts as refused: "we could not tell" is not permission.

⚠⚠ Not only RFC 1918. net.IP.IsPrivate calls the shared address space 100.64.0.0/10 public, and that is where overlay meshes live (Cloudflare WARP 100.96/12, Tailscale, carrier-grade NAT). The other ranges below are not routable destinations either, and the IPv6 spellings that wrap an IPv4 address (NAT64, 6to4) are judged by the address they wrap.

func Transport

func Transport(responseHeaderTimeout time.Duration) *http.Transport

Transport returns an http.Transport that dials through the guard. Callers set their own timeouts on the fields they care about; proxies are off, because a proxy would do the resolving and the guard would never see it.

Types

type Policy added in v0.50.0

type Policy struct {
	// Loopback lets a download reach this machine - 127.0.0.0/8 and ::1,
	// nothing else that is private. Development and the end-to-end tests serve
	// plugin sources from a loopback server (FILEX_PLUGIN_LOOPBACK_SOURCES); a
	// server in service never needs it, and with it on, a manifest that names
	// a loopback address makes filex send GET requests to its own services.
	Loopback bool
}

Policy widens what a guarded dial may reach beyond the public internet. The zero value is the guard itself: public addresses only. An app's own requests (http_request, asset_fetch) always use the zero value; only the plugin DOWNLOADS (DownloadClient) take one from configuration.

func (Policy) APIClient added in v0.52.0

func (p Policy) APIClient(timeout time.Duration) *http.Client

APIClient is DownloadClient's sibling for an API filex SENDS something to - a body, a secret (an app store's license check carries a license key) - and reads an answer from: every dial guarded the same way, but a redirect is never followed. It is the answer (http.ErrUseLastResponse), so a 307 or 308 cannot make the request, body and all, go to whatever host the response names (an open redirect at the API, a hijacked route).

func (Policy) DialContext added in v0.50.0

func (p Policy) DialContext(dialer *net.Dialer) func(ctx context.Context, network, addr string) (net.Conn, error)

DialContext is the guarded dial under this policy.

func (Policy) DownloadClient added in v0.50.0

func (p Policy) DownloadClient(timeout time.Duration) *http.Client

DownloadClient is the ONE client every plugin download goes through: a storage plugin's binary, its source's feed and build, an app's manifest, module and interface bundle - for an administrator's install, an install request an API key leaves, and the daily update check alike. Each dial is guarded (after DNS, so a public name that resolves inward is refused too), every redirect hop dials through the same guard, the chain is capped at MaxRedirects, and a redirect never leaves https for plain http: a download that started encrypted is not finished in the clear, where anything on the path may answer instead. timeout bounds the whole download, body included; the callers cap the size.

func (Policy) Refused added in v0.50.0

func (p Policy) Refused(ip net.IP) bool

Refused is the package-level Refused with the policy's one exception.

func (Policy) Transport added in v0.50.0

func (p Policy) Transport(responseHeaderTimeout time.Duration) *http.Transport

Transport is the guarded transport under this policy.

Jump to

Keyboard shortcuts

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