JuliusBrussee/caveman · error
listen address is not loopback; standalone proxy has no…
Error message
listen address %q is not loopback; standalone proxy has no inbound authentication; set CAVEMAN_AUTH_TOKEN to expose the proxy beyond loopback
What it means
The listen host parses as a non-loopback IP (or fails to parse as an IP at all) while CAVEMAN_AUTH_TOKEN is unset, so the standalone proxy would be exposed with no inbound authentication. validateListen blocks this unless an auth token is configured, turning a wide bind into a deliberate operator choice. Note: when authenticated is true, a non-loopback bind is allowed and the host is also not required to be a parseable IP.
Solutions
- Set CAVEMAN_AUTH_TOKEN to a strong token (≥ minAuthTokenBytes, no spaces/control chars) to deliberately allow the wide bind.
- Otherwise change listen to a loopback address: "127.0.0.1:8080", "localhost:8080", or "[::1]:8080".
- If remote access is needed but not authentication, front the proxy with a reverse proxy that enforces auth and keep this proxy loopback-only.
Example fix
// before listen = "0.0.0.0:8080" # no CAVEMAN_AUTH_TOKEN // after listen = "127.0.0.1:8080" // or, deliberately exposed: // listen = "0.0.0.0:8080" + CAVEMAN_AUTH_TOKEN=<strong secret>
Defensive patterns
Strategy: validation
Validate before calling
host, _, err := net.SplitHostPort(strings.TrimSpace(listen))
if err == nil && host != "localhost" {
ip := net.ParseIP(host)
if (ip == nil || !ip.IsLoopback()) && os.Getenv("CAVEMAN_AUTH_TOKEN") == "" {
return fmt.Errorf("non-loopback listen %q requires CAVEMAN_AUTH_TOKEN", listen)
}
} Prevention
- Default to loopback binds unless remote access is an explicit requirement.
- Pair any non-loopback bind with a strong CAVEMAN_AUTH_TOKEN in the same change.
- Document the security tradeoff in runbooks for LAN/container deployments.
When it happens
Trigger: Setting listen to "0.0.0.0:8080", "192.168.1.10:8080", or a hostname that is not "localhost" and not a parseable loopback IP, without CAVEMAN_AUTH_TOKEN set, then calling Load.
Common situations: Trying to reach the proxy from another machine or container on the LAN; Docker setups binding a wide interface; hostnames like "proxy.local" that net.ParseIP rejects.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- auth_token: in is ignored — the inbound token is read only…
- CAVEMAN_AUTH_TOKEN must be at least
- caveman build: config must use strict lock and required…
- compat upstream forward_headers
- production requires an https:// CLICKHOUSE_URL (TLS only)…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/8da6d7ecd13400ec.
Report an issue: GitHub.
Appendix: source
Thrown at proxy/internal/config/config.go:327
// inbound credential gates it. Binding an empty, wildcard, or non-loopback host
// would expose every configured provider credential to the network with no
// inbound authentication; authenticated says CAVEMAN_AUTH_TOKEN is set, so
// standalone.Auth rejects every request that does not present it and the wider
// bind becomes a deliberate operator choice instead of an accident.
func validateListen(listen string, authenticated bool) error {
host, port, err := net.SplitHostPort(strings.TrimSpace(listen))
if err != nil || port == "" {
return fmt.Errorf("listen address %q must be loopback host:port", listen)
}
if strings.EqualFold(host, "localhost") {
return nil
}
ip := net.ParseIP(host)
if ip == nil || !ip.IsLoopback() {
if authenticated {
return nil
}
return fmt.Errorf("listen address %q is not loopback; standalone proxy has no inbound authentication; set CAVEMAN_AUTH_TOKEN to expose the proxy beyond loopback", listen)
}
return nil
}
func (c Config) withDefaults() Config {
if label := env.String("CAVEMAN_LABEL", ""); label != "" {
c.Label = label
}
if c.Label == "" {
c.Label = "local"
}
if mode := env.String("CAVEMAN_MODE", ""); mode != "" {
c.Mode = mode
}
if listen := env.String("CAVEMAN_LISTEN", ""); listen != "" {
c.Listen = listen
}
// Assigned unconditionally: the environment is the ONLY source for thisView on GitHub (pinned to 3ee70a1026)