Documentation
¶
Overview ¶
Package linkcheck implements application.ExternalLinkChecker: an opt-in (--check-external) HTTP liveness checker for external http(s) links, with a mandatory SSRF guard (ADR 0003). It is the ONLY package that performs outbound network requests, and it lives in infrastructure so the domain stays free of net/http (ADR 0004).
Security model (ADR 0003 invariant 6). Before any request, and again on every redirect hop, the checker:
- rejects non-http(s) schemes (file://, data:, gopher:// … are never fetched);
- resolves the host to IPs and rejects the request if ANY resolved IP is loopback, link-local, the cloud metadata address (169.254.169.254), unique-local, or in a private RFC1918 range — checking the RESOLVED IP (not just the literal host) defeats DNS-rebinding-to-internal;
- honors an explicit allow/deny list of host:port or IP/CIDR before the range checks.
It bounds concurrency, applies a per-host minimum interval (rate limit) and a per-request timeout, caps redirects, and de-duplicates URLs so the same URL is never fetched twice. It is fully injectable: the HTTP transport and the DNS resolver are interfaces so tests drive it with an httptest server and a fake resolver and assert that internal targets are refused WITHOUT a network call.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Checker ¶
type Checker struct {
// contains filtered or unexported fields
}
Checker validates external URLs. Construct with New. It implements application.ExternalLinkChecker.
func New ¶
New builds a Checker with the SSRF guard wired in. Defaults are filled for any zero Config field.
func (*Checker) Check ¶
func (c *Checker) Check(ctx context.Context, urls []string) map[string]application.ExternalResult
Check validates each URL (de-duplicated) with bounded concurrency and returns a result keyed by the input URL string. Determinism of output is not required (external results are excluded from the default deterministic artifacts), but the map is complete: every input URL gets a result.
type Config ¶
type Config struct {
// Concurrency bounds in-flight requests (default 8, min 1).
Concurrency int
// Timeout is the per-request timeout (default 5s).
Timeout time.Duration
// MaxRedirects caps redirect hops; each hop is re-checked by the SSRF guard.
// Zero-value semantics: 0 means "use the default of 5" (so the safe-by-default
// zero Config still follows a sensible number of hops); a negative value
// (e.g. -1) means "follow NO redirects" (the cap becomes 0 hops, so any
// redirect is refused). See New, which normalizes these.
MaxRedirects int
// PerHostInterval is the minimum spacing between requests to the same host
// (rate limit; default 200ms). Zero disables rate limiting.
PerHostInterval time.Duration
// Allow is an explicit allowlist of host[:port] or IP/CIDR entries. A URL
// whose host/IP matches an Allow entry skips the private-range guard (the
// operator vouches for it). Allow is checked before Deny and before ranges.
Allow []string
// Deny is an explicit denylist of host[:port] or IP/CIDR entries, checked
// before the range guard; a match is always refused.
Deny []string
}
Config tunes a Checker. The zero value is safe; New fills defaults.
type Option ¶
type Option func(*Checker)
Option customizes a Checker (mainly for tests: inject a transport/resolver).
func WithResolver ¶
WithResolver injects a custom DNS resolver (tests use a fake).
func WithTransport ¶
func WithTransport(rt http.RoundTripper) Option
WithTransport injects a custom http.RoundTripper (tests point it at an httptest server). The checker still installs its own redirect guard.