{"record":{"id":"8da6d7ecd13400ec","repo":"JuliusBrussee/caveman","slug":"listen-address-q-is-not-loopback-standalone-proxy-has-no","errorCode":null,"errorMessage":"listen address %q is not loopback; standalone proxy has no inbound authentication; set CAVEMAN_AUTH_TOKEN to expose the proxy beyond loopback","messagePattern":"listen address %q is not loopback; standalone proxy has no inbound authentication; set CAVEMAN_AUTH_TOKEN to expose the proxy beyond loopback","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/config/config.go","lineNumber":327,"sourceCode":"// inbound credential gates it. Binding an empty, wildcard, or non-loopback host\n// would expose every configured provider credential to the network with no\n// inbound authentication; authenticated says CAVEMAN_AUTH_TOKEN is set, so\n// standalone.Auth rejects every request that does not present it and the wider\n// bind becomes a deliberate operator choice instead of an accident.\nfunc validateListen(listen string, authenticated bool) error {\n\thost, port, err := net.SplitHostPort(strings.TrimSpace(listen))\n\tif err != nil || port == \"\" {\n\t\treturn fmt.Errorf(\"listen address %q must be loopback host:port\", listen)\n\t}\n\tif strings.EqualFold(host, \"localhost\") {\n\t\treturn nil\n\t}\n\tip := net.ParseIP(host)\n\tif ip == nil || !ip.IsLoopback() {\n\t\tif authenticated {\n\t\t\treturn nil\n\t\t}\n\t\treturn 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)\n\t}\n\treturn nil\n}\n\nfunc (c Config) withDefaults() Config {\n\tif label := env.String(\"CAVEMAN_LABEL\", \"\"); label != \"\" {\n\t\tc.Label = label\n\t}\n\tif c.Label == \"\" {\n\t\tc.Label = \"local\"\n\t}\n\tif mode := env.String(\"CAVEMAN_MODE\", \"\"); mode != \"\" {\n\t\tc.Mode = mode\n\t}\n\tif listen := env.String(\"CAVEMAN_LISTEN\", \"\"); listen != \"\" {\n\t\tc.Listen = listen\n\t}\n\t// Assigned unconditionally: the environment is the ONLY source for this","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/internal/config/config.go#L309-L345","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlisten = \"0.0.0.0:8080\"   # no CAVEMAN_AUTH_TOKEN\n// after\nlisten = \"127.0.0.1:8080\"\n// or, deliberately exposed:\n// listen = \"0.0.0.0:8080\" + CAVEMAN_AUTH_TOKEN=<strong secret>","handlingStrategy":"validation","validationCode":"host, _, err := net.SplitHostPort(strings.TrimSpace(listen))\nif err == nil && host != \"localhost\" {\n    ip := net.ParseIP(host)\n    if (ip == nil || !ip.IsLoopback()) && os.Getenv(\"CAVEMAN_AUTH_TOKEN\") == \"\" {\n        return fmt.Errorf(\"non-loopback listen %q requires CAVEMAN_AUTH_TOKEN\", listen)\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["config","security","loopback","auth"],"backgroundTag":"invalid-config-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}