Documentation
¶
Overview ¶
Package netsec provides centralized network security primitives used by every HTTP-based provider in SpeechKit (STT, TTS, LLM, downloads).
It addresses the audit findings S1-S5 (2026-04-16):
- SSRF via unvalidated user-supplied BaseURL configuration
- Missing TLS hardening on HTTP clients
- Bearer tokens leaking through default loggers
All HTTP provider constructors MUST route user-supplied URLs through ValidateProviderURL and MUST obtain their *http.Client via NewSafeHTTPClient.
Index ¶
- Constants
- Variables
- func BuildEndpoint(baseURL, path string, opts ValidationOptions) (string, error)
- func NewSafeHTTPClient(opts ClientOptions) *http.Client
- func ProviderStatusError(provider string, status int, body []byte) error
- func RedactBearer(value string) string
- func RedactHeaders(h http.Header) http.Header
- func SafeProviderErrorReason(status int, body []byte) string
- func ValidateProviderURL(raw string, opts ValidationOptions) error
- func ValidateResolvedIP(ip net.IP, opts ValidationOptions) error
- type ClientOptions
- type RedactingRoundTripper
- type ValidationOptions
Constants ¶
const RedactedHeadersKey ctxKey = 1
RedactedHeadersKey is the context key used to store redacted headers.
Variables ¶
var ( ErrEmptyURL = errors.New("netsec: empty URL") ErrInvalidURL = errors.New("netsec: URL could not be parsed") ErrMissingScheme = errors.New("netsec: URL must have a scheme") ErrMissingHost = errors.New("netsec: URL must have a host") ErrUnsupportedScheme = errors.New("netsec: scheme must be http or https") ErrInsecureHTTP = errors.New("netsec: plain http:// not allowed for this host") ErrLoopbackBlocked = errors.New("netsec: loopback addresses not allowed") ErrPrivateBlocked = errors.New("netsec: private / link-local / ULA addresses not allowed") ErrInvalidHost = errors.New("netsec: URL host could not be resolved as a literal IP or name") ErrUserInfoForbidden = errors.New("netsec: URL user-info (user:pass@) is not permitted") )
Validation errors. Callers can match on these with errors.Is for UX.
Functions ¶
func BuildEndpoint ¶
func BuildEndpoint(baseURL, path string, opts ValidationOptions) (string, error)
BuildEndpoint validates baseURL and joins path safely. It prevents a malicious baseURL like "https://legit.example.com/.." from escaping the host, and normalises trailing slashes.
func NewSafeHTTPClient ¶
func NewSafeHTTPClient(opts ClientOptions) *http.Client
NewSafeHTTPClient returns an *http.Client with explicit TLS 1.2+ minimum, sensible timeouts, optional dial-target validation, and a transport wrapper that stores redacted request headers in context for logging paths that opt in.
The client is safe to share across goroutines.
func ProviderStatusError ¶ added in v0.24.0
ProviderStatusError returns a user-safe upstream provider error. It keeps the provider name and HTTP status, but never includes the raw response body.
func RedactBearer ¶
RedactBearer returns a safe rendering of an "Authorization: Bearer xxx" header value for logs. It always returns "Bearer [REDACTED]" when the input is non-empty, and "" otherwise.
func RedactHeaders ¶
RedactHeaders returns a copy of h where sensitive header values are replaced with the literal "[REDACTED]". Safe for logging.
func SafeProviderErrorReason ¶ added in v0.24.0
SafeProviderErrorReason classifies a provider response body without exposing body contents to UI, logs, or higher-level error messages.
func ValidateProviderURL ¶
func ValidateProviderURL(raw string, opts ValidationOptions) error
ValidateProviderURL parses raw and rejects URLs that would expose the caller to SSRF. It does not make network calls — hostnames that are not IP literals are checked by NewSafeHTTPClient when DialValidation is set. Provider, download and update clients should combine URL validation with resolve-time dial validation.
func ValidateResolvedIP ¶ added in v0.24.0
func ValidateResolvedIP(ip net.IP, opts ValidationOptions) error
ValidateResolvedIP applies SSRF range restrictions to a concrete IP address returned by DNS resolution or visible on a dial target.
Types ¶
type ClientOptions ¶
type ClientOptions struct {
// Timeout is the per-request timeout. Zero disables it (discouraged).
Timeout time.Duration
// TLSMinVersion pins the minimum TLS version (default: tls.VersionTLS12).
TLSMinVersion uint16
// InnerTransport, if non-nil, is wrapped by the RedactingRoundTripper.
// Normally nil — the function constructs a hardened *http.Transport.
InnerTransport http.RoundTripper
// DisableKeepAlives turns off connection reuse. Leave false for best perf.
DisableKeepAlives bool
// DialValidation, when non-nil, validates the actual resolved target IP
// before opening a TCP connection. Use this for any client that might reach
// user-configurable provider, model download, or update URLs.
DialValidation *ValidationOptions
}
ClientOptions configures a SpeechKit HTTP client.
type RedactingRoundTripper ¶
type RedactingRoundTripper struct {
Base http.RoundTripper
}
RedactingRoundTripper is an http.RoundTripper that records redacted copies of the outgoing request header set on the *http.Request context so that downstream logging middleware can inspect non-sensitive headers only.
It does NOT rewrite the outgoing request — the real Authorization header is sent over TLS as expected. Redaction applies only to what observability paths can access via ctx.Value(RedactedHeadersKey).
type ValidationOptions ¶
type ValidationOptions struct {
// AllowLoopback permits http:// and https:// URLs whose host is a
// literal loopback address (127.0.0.0/8, ::1, or the name "localhost").
// Enable for local whisper-server / Ollama / dev endpoints.
AllowLoopback bool
// AllowPrivate permits URLs whose host is in RFC1918 / RFC6598 /
// link-local / unique-local IPv6 ranges. Enable only for self-hosted
// VPS scenarios where the user explicitly opts in.
AllowPrivate bool
// AllowHTTP permits http:// for non-loopback hosts. Leave false unless
// an operator has a documented reason (e.g. test harness).
AllowHTTP bool
}
ValidationOptions controls how strict URL validation behaves. The zero value is the safe default: HTTPS-only, no loopback, no private IPs.