Unset TRUSTED_PROXIES trusts RFC 1918, empty trusts none (closes #129)
check / check (push) Canceled after 0s

The backend's own TRUSTED_PROXIES now follows the ruling for every app:
unset, it trusts 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16, and no
longer loopback; a list replaces that; set and empty, it trusts no
proxy.

viper reads an empty environment variable as unset, so an empty value
got the default. config.New now reads this one setting with
os.LookupEnv first. viper's AllowEmptyEnv would apply to every setting,
so an empty PORT or DEBUG would stop the start instead of counting as
unset, as README.md says it does.

bin/entrypoint.sh still starts the backend with
TRUSTED_PROXIES=127.0.0.1/32.

Model: opus-5-5
This commit was merged in pull request #136.
This commit is contained in:
2026-10-14 14:01:37 +02:00
parent cff7b07bbc
commit 2a47fb9a8e
4 changed files with 89 additions and 14 deletions
+13 -6
View File
@@ -9,6 +9,7 @@ import (
"math"
"net/netip"
"net/url"
"os"
"strconv"
"strings"
@@ -21,13 +22,11 @@ import (
)
// defaultTrustedProxies lists the networks whose forwarded
// headers are honoured by default: IPv4 and IPv6 loopback,
// for a reverse proxy on the same host, and the RFC1918
// ranges. The container image does not use it:
// headers are honoured when TRUSTED_PROXIES is unset: the
// RFC 1918 ranges. The container image does not use it:
// bin/entrypoint.sh gives the server 127.0.0.1/32, since
// nginx is its only client there.
const defaultTrustedProxies = "127.0.0.1/32,::1/128," +
"10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
const defaultTrustedProxies = "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
// Default limits on stored reports; README.md gives the reasons for
// these values.
@@ -135,6 +134,14 @@ func New(
viper.GetString("DEBUG"), errNotBool)
}
// An empty TRUSTED_PROXIES trusts no proxy, but viper reads an
// empty environment variable as unset and would give the default,
// so the environment is read first.
trustedProxies, set := os.LookupEnv("TRUSTED_PROXIES")
if !set {
trustedProxies = viper.GetString("TRUSTED_PROXIES")
}
s := &Config{
BindAddress: viper.GetString("BIND_ADDRESS"),
CORSAllowedOrigins: splitList(viper.GetString("CORS_ALLOWED_ORIGINS")),
@@ -146,7 +153,7 @@ func New(
Port: port,
ReportsPerMinute: viper.GetInt("REPORTS_PER_MINUTE"),
SentryDSN: viper.GetString("SENTRY_DSN"),
TrustedProxies: splitList(viper.GetString("TRUSTED_PROXIES")),
TrustedProxies: splitList(trustedProxies),
log: log,
params: &params,
}
+58
View File
@@ -1,6 +1,8 @@
package config_test
import (
"os"
"slices"
"strings"
"testing"
@@ -29,6 +31,62 @@ func requireConfigError(t *testing.T, setting string) {
}
}
// requireTrustedProxies builds the config as main does and fails the
// test unless its TrustedProxies are want.
func requireTrustedProxies(t *testing.T, want []string) {
t.Helper()
var cfg *config.Config
app := fx.New(
fx.NopLogger,
fx.Provide(globals.New, logger.New, config.New),
fx.Populate(&cfg),
)
err := app.Err()
if err != nil {
t.Fatalf("config error = %v", err)
}
if !slices.Equal(cfg.TrustedProxies, want) {
t.Fatalf("TrustedProxies = %q, want %q", cfg.TrustedProxies, want)
}
}
// TestTrustedProxiesUnsetTrustsRFC1918: unset, the server trusts the
// RFC 1918 ranges and nothing else, loopback included.
func TestTrustedProxiesUnsetTrustsRFC1918(t *testing.T) {
// t.Setenv first, so the variable is put back as it was after the
// test.
t.Setenv("TRUSTED_PROXIES", "")
err := os.Unsetenv("TRUSTED_PROXIES")
if err != nil {
t.Fatal(err)
}
requireTrustedProxies(t,
[]string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"})
}
// TestTrustedProxiesEmptyTrustsNothing: set and empty, the server
// trusts no proxy. viper alone would read the empty variable as unset
// and give the default.
func TestTrustedProxiesEmptyTrustsNothing(t *testing.T) {
t.Setenv("TRUSTED_PROXIES", "")
requireTrustedProxies(t, []string{})
}
// TestTrustedProxiesListReplacesDefault: a list is used as given, with
// none of the default ranges added.
func TestTrustedProxiesListReplacesDefault(t *testing.T) {
t.Setenv("TRUSTED_PROXIES", "172.17.0.1/32, 2001:db8::/32")
requireTrustedProxies(t, []string{"172.17.0.1/32", "2001:db8::/32"})
}
// TestSettingsLoadAsGiven: valid values pass the checks and are used
// as given. bin/entrypoint.sh starts the server with these
// BIND_ADDRESS and PORT values.