linkcheck

package
v0.0.6 Latest Latest
Warning

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

Go to latest
Published: Jun 23, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

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

func New(cfg Config, opts ...Option) *Checker

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

func WithResolver(r Resolver) Option

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.

type Resolver

type Resolver interface {
	LookupIP(ctx context.Context, host string) ([]net.IP, error)
}

Resolver looks up the IP addresses for a host. The standard implementation wraps net.Resolver; tests inject a fake to exercise the guard deterministically (including DNS-rebinding: a public-looking host that resolves to a private IP).

Jump to

Keyboard shortcuts

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