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 RedactBearer(value string) string
- func RedactHeaders(h http.Header) http.Header
- func ValidateProviderURL(raw string, 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, and a RedactingRoundTripper wrapper so that any subsequent call to httputil.DumpRequest will not leak Authorization headers.
The client is safe to share across goroutines.
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 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 accepted as public, on the assumption that DNS resolution happens inside the stdlib http.Transport. If you need resolve-time protection, combine with a RestrictedDialer (see httpclient.go).
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
}
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.