{"record":{"id":"2255303a268092dd","repo":"JuliusBrussee/caveman","slug":"listen-address-q-must-be-loopback-host-port","errorCode":null,"errorMessage":"listen address %q must be loopback host:port","messagePattern":"listen address %q must be loopback host:port","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/config/config.go","lineNumber":126,"sourceCode":"\t\t}\n\t}\n\tcfg = cfg.withDefaults()\n\tif err := validateListen(cfg.Listen); err != nil {\n\t\treturn Config{}, err\n\t}\n\tif err := cfg.validateCompat(); err != nil {\n\t\treturn Config{}, err\n\t}\n\treturn cfg, nil\n}\n\n// validateListen keeps standalone's unauthenticated BYOK proxy local to one\n// operator. Binding an empty, wildcard, or non-loopback host would expose every\n// configured provider credential to the network with no inbound authentication.\nfunc validateListen(listen string) 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\treturn fmt.Errorf(\"listen address %q is not loopback; standalone proxy has no inbound authentication\", 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}","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/config/config.go#L108-L144","documentation":"config.validateListen rejects the proxy's listen address before startup when it cannot be split into a host and a non-empty port. The caveman standalone proxy is unauthenticated BYOK, so the address must be a concrete loopback host:port. Malformed input such as '127.0.0.1' (no port), ':8080' (no host usable later), or extra colons fails here.","triggerScenarios":"Setting listen (caveman.yaml or CAVEMAN_LISTEN env) to a host without a port ('localhost'), a port without a host (':9111' parses but usually fails the loopback check), '127.0.0.1:8080:extra', or a port string that SplitHostPort rejects entirely.","commonSituations":"Copying a bare hostname from docs into the listen field; Docker-minded habits of binding ':PORT'; trailing whitespace variants that SplitHostPort still fails on; IPv6 addresses written without brackets ('::1:8080' instead of '[::1]:8080').","solutions":["Set listen to an explicit loopback host:port, e.g. '127.0.0.1:8080', 'localhost:8080', or '[::1]:8080'","If you need a different port only, keep the host loopback and change just the port","Wrap IPv6 loopback in brackets so SplitHostPort parses the port correctly","To expose the proxy beyond loopback, put an authenticated reverse proxy in front instead of widening the bind address"],"exampleFix":"// before\nlisten: \"127.0.0.1\"   // Error[1060]: must be loopback host:port\n\n// after\nlisten: \"127.0.0.1:8080\"","handlingStrategy":"validation","validationCode":"// validateListenLike mirrors config.validateListen's parse step.\nfunc checkListenAddr(listen string) error {\n    host, port, err := net.SplitHostPort(strings.TrimSpace(listen))\n    if err != nil || port == \"\" {\n        return fmt.Errorf(\"listen %q: missing host:port\", listen)\n    }\n    return nil\n}\n\nif err := checkListenAddr(cfg.Listen); err != nil { /* fix config before start */ }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Hardcode the full loopback host:port ('127.0.0.1:8080') in templates and docs","Bracket IPv6 hosts: '[::1]:8080'","Lint caveman.yaml in CI with the same SplitHostPort check used in production"],"tags":["config","network","security","startup"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}