{"record":{"id":"7d26f577832a70fe","repo":"gastownhall/beads","slug":"allowed-host-q-contains-whitespace-it-must-be","errorCode":null,"errorMessage":"--allowed-host %q contains whitespace; it must be a bare host name or IP","messagePattern":"--allowed-host %q contains whitespace; it must be a bare host name or IP","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/httpapi/server.go","lineNumber":1862,"sourceCode":"\t\t\tcontinue\n\t\t}\n\t\tp.names[h] = true\n\t}\n\treturn p\n}\n\n// ValidateAllowedHost refuses an allowlist entry that is not a bare host.\n//\n// The Host header's port is stripped before matching (hostOnly), so an entry\n// carrying one would silently never match — and an operator who wrote it would\n// reasonably read the startup line as proof that it does. A URL, a path or\n// embedded whitespace is the same mistake in a louder form.\nfunc ValidateAllowedHost(v string) error {\n\tif strings.TrimSpace(v) == \"\" {\n\t\treturn errors.New(\"--allowed-host is empty; pass the Host header value clients send, such as bd-myproject.beads.svc.cluster.local\")\n\t}\n\tif strings.ContainsAny(v, \" \\t\\r\\n\") {\n\t\treturn fmt.Errorf(\"--allowed-host %q contains whitespace; it must be a bare host name or IP\", v)\n\t}\n\tif strings.ContainsAny(v, \"/@\") {\n\t\treturn fmt.Errorf(\"--allowed-host %q looks like a URL; pass just the host, with no scheme and no path\", v)\n\t}\n\t// An IPv6 address is spelled in brackets in a Host header, so an operator\n\t// copying one off the wire types it that way. hostOnly strips them before\n\t// matching, so the entry works; refusing it here — with a message about a\n\t// port it does not have — would be the validation lying about the policy.\n\tif net.ParseIP(strings.TrimSuffix(strings.TrimPrefix(v, \"[\"), \"]\")) != nil {\n\t\treturn nil\n\t}\n\tif strings.Contains(v, \":\") {\n\t\treturn fmt.Errorf(\"--allowed-host %q carries a port; the port is stripped from a request's Host before matching, so an entry with one could never match\", v)\n\t}\n\treturn nil\n}\n\n// allows reports whether a Host header value is one this server answers to.","sourceCodeStart":1844,"sourceCodeEnd":1880,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/httpapi/server.go#L1844-L1880","documentation":"ValidateAllowedHost rejects entries containing whitespace. The allowed-host entry must be a bare host name or IP because it is compared against the request's Host header; whitespace would guarantee a mismatch and indicates a copy/paste or quoting mistake.","triggerScenarios":"Calling ValidateAllowedHost with values like \"bd.example.com\\n\", \"example.com, example2.com\" (comma+space lists), or a host with a trailing space from shell quoting.","commonSituations":"Operators paste multiple hosts on one flag separated by spaces, or a config file value picks up a trailing newline or indentation.","solutions":["Pass one bare host per flag occurrence; repeat --allowed-host for multiple entries","Trim whitespace/newlines from config or env values before passing","Use commas only if the CLI documents list parsing — otherwise supply each host separately"],"exampleFix":"// before\n--allowed-host \"a.example.com b.example.com\"\n// after\n--allowed-host a.example.com --allowed-host b.example.com","handlingStrategy":"validation","validationCode":"if strings.ContainsAny(v, \" \\t\\r\\n\") {\n    return fmt.Errorf(\"strip whitespace from %q before passing --allowed-host\", v)\n}","typeGuard":null,"tryCatchPattern":"if err := httpapi.ValidateAllowedHost(v); err != nil {\n    log.Fatalf(\"invalid --allowed-host %q: %v\", v, err)\n}","preventionTips":["Trim config/env values before passing","Repeat --allowed-host per host instead of space-separated lists","Quote shell args carefully to avoid trailing spaces"],"tags":["validation","cli","http","host-header"],"backgroundTag":"invalid-allowed-host","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}