From c5f4682b0b2063420abb110e47e9c981fa9f3cf5 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 21 Sep 2026 23:23:47 +0000 Subject: [PATCH] feat: add trusted_proxies config key Add a trusted_proxies CIDR-list config key alongside blocked_networks. Generalize the blocked_networks parser into parseCIDRList and cidrListEntries, which take the key name as a parameter, so both keys share one parser rather than a second copy. An invalid entry aborts startup naming the key and value; an omitted or empty key leaves the list empty. Model: opus-4-8 --- internal/config/config.go | 54 +++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index c8c1d60..324f026 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -44,6 +44,7 @@ const ( keyUpstreamConnectionsPerHost = "upstream_connections_per_host" keyCacheMaxBytes = "cache_max_bytes" keyBlockedNetworks = "blocked_networks" + keyTrustedProxies = "trusted_proxies" ) // placeholderSigningKey is the dummy signing_key shipped in @@ -117,6 +118,14 @@ type Config struct { // fetcher's dialer; the built-in ranges always apply. BlockedNetworks []netip.Prefix + // TrustedProxies are the CIDR ranges of reverse proxies whose + // forwarding headers may be believed. Forwarded headers are honored + // only when the immediate peer falls inside one of these ranges; + // otherwise the peer address is used and the headers are ignored, so + // an untrusted client cannot spoof its address. Empty means trust + // nothing and always use the peer address. + TrustedProxies []netip.Prefix + // CacheMaxBytes is the disk cache size limit in bytes. Zero // disables the disk cache entirely. When cache_max_bytes is // omitted from the configuration, this holds the computed default @@ -185,7 +194,12 @@ func newFromSmartConfig(sc *smartconfig.Config) (*Config, error) { } } - blockedNetworks, err := getBlockedNetworks(sc) + blockedNetworks, err := parseCIDRList(sc, keyBlockedNetworks) + if err != nil { + return nil, err + } + + trustedProxies, err := parseCIDRList(sc, keyTrustedProxies) if err != nil { return nil, err } @@ -207,6 +221,7 @@ func newFromSmartConfig(sc *smartconfig.Config) (*Config, error) { keyUpstreamConnectionsPerHost, DefaultUpstreamConnectionsPerHost), CacheMaxBytes: loader.int64Val(keyCacheMaxBytes, 0), BlockedNetworks: blockedNetworks, + TrustedProxies: trustedProxies, } // The computed default for cache_max_bytes needs a validated @@ -322,7 +337,8 @@ func isKnownConfigKey(key string) bool { switch key { case keyDebug, keyMaintenanceMode, keyPort, keyStateDir, keySentryDSN, keyDBURL, keyMetrics, keySigningKey, keyAllowlistHosts, keyAllowHTTP, - keyUpstreamConnectionsPerHost, keyCacheMaxBytes, keyBlockedNetworks, "env": + keyUpstreamConnectionsPerHost, keyCacheMaxBytes, keyBlockedNetworks, + keyTrustedProxies, "env": return true } @@ -817,27 +833,27 @@ func getStringSlice(sc *smartconfig.Config) []string { return nil } -// getBlockedNetworks parses the blocked_networks value into CIDR prefixes, -// or returns nil if the key is omitted. It accepts a YAML list of strings -// or a comma-separated string. An explicitly null value, a wrong type, an -// empty entry, a non-string entry, or an unparseable CIDR aborts startup -// naming the key and the offending value; a default (the built-in -// blocklist alone) applies only to an omitted key. -func getBlockedNetworks(sc *smartconfig.Config) ([]netip.Prefix, error) { +// parseCIDRList parses the value of the named config key into CIDR +// prefixes, or returns nil if the key is omitted. It accepts a YAML list +// of strings or a comma-separated string. An explicitly null value, a +// wrong type, an empty entry, a non-string entry, or an unparseable CIDR +// aborts startup naming the key and the offending value; the default +// (an empty list) applies only to an omitted key. +func parseCIDRList(sc *smartconfig.Config, key string) ([]netip.Prefix, error) { if sc == nil { return nil, nil } - raw, ok := sc.Get(keyBlockedNetworks) + raw, ok := sc.Get(key) if !ok { return nil, nil } if raw == nil { - return nil, errNullConfigValue(keyBlockedNetworks) + return nil, errNullConfigValue(key) } - entries, err := blockedNetworkEntries(raw) + entries, err := cidrListEntries(raw, key) if err != nil { return nil, err } @@ -848,7 +864,7 @@ func getBlockedNetworks(sc *smartconfig.Config) ([]netip.Prefix, error) { prefix, err := netip.ParsePrefix(entry) if err != nil { return nil, fmt.Errorf("config key %q: value %q is %w", - keyBlockedNetworks, entry, errNotAValidCIDR) + key, entry, errNotAValidCIDR) } prefixes = append(prefixes, prefix) @@ -857,10 +873,10 @@ func getBlockedNetworks(sc *smartconfig.Config) ([]netip.Prefix, error) { return prefixes, nil } -// blockedNetworkEntries extracts the raw blocked_networks entries as +// cidrListEntries extracts the raw entries of the named CIDR-list key as // trimmed, non-empty strings, from either a YAML list of strings or a // comma-separated string. Any other shape is a configuration error. -func blockedNetworkEntries(raw any) ([]string, error) { +func cidrListEntries(raw any, key string) ([]string, error) { switch val := raw.(type) { case []any: entries := make([]string, 0, len(val)) @@ -869,12 +885,12 @@ func blockedNetworkEntries(raw any) ([]string, error) { str, ok := item.(string) if !ok { return nil, fmt.Errorf("config key %q: list entry %v (%T) is %w", - keyBlockedNetworks, item, item, errNotAString) + key, item, item, errNotAString) } if strings.TrimSpace(str) == "" { return nil, fmt.Errorf("config key %q: %w", - keyBlockedNetworks, errEmptyListEntry) + key, errEmptyListEntry) } entries = append(entries, strings.TrimSpace(str)) @@ -888,7 +904,7 @@ func blockedNetworkEntries(raw any) ([]string, error) { trimmed := strings.TrimSpace(part) if trimmed == "" { return nil, fmt.Errorf("config key %q: value %q %w", - keyBlockedNetworks, val, errEmptyEntry) + key, val, errEmptyEntry) } entries = append(entries, trimmed) @@ -897,6 +913,6 @@ func blockedNetworkEntries(raw any) ([]string, error) { return entries, nil default: return nil, fmt.Errorf("config key %q: value %v (%T) is %w", - keyBlockedNetworks, raw, raw, errNotAStringList) + key, raw, raw, errNotAStringList) } }