{"record":{"id":"9d825708a71f06d0","repo":"juanfont/headscale","slug":"input-must-contain-a-colon-character-separating-de","errorCode":null,"errorMessage":"input must contain a colon character separating destination and port","messagePattern":"input must contain a colon character separating destination and port","errorType":"validation","errorClass":"ErrInputMissingColon","httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/utils.go","lineNumber":16,"sourceCode":"package v2\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"tailscale.com/tailcfg\"\n)\n\n// Port parsing errors.\nvar (\n\tErrInputMissingColon      = errors.New(\"input must contain a colon character separating destination and port\")\n\tErrInputStartsWithColon   = errors.New(\"input cannot start with a colon character\")\n\tErrInputEndsWithColon     = errors.New(\"input cannot end with a colon character\")\n\tErrInvalidPortRangeFormat = errors.New(\"invalid port range format\")\n\tErrPortRangeInverted      = errors.New(\"invalid port range: first port is greater than last port\")\n\tErrPortMustBePositive     = errors.New(\"first port must be >0, or use '*' for wildcard\")\n\tErrInvalidPortNumber      = errors.New(\"invalid first integer\")\n\tErrPortNumberOutOfRange   = errors.New(\"port number out of range\")\n\tErrBracketsNotIPv6        = errors.New(\"square brackets are only valid around IPv6 addresses\")\n)\n\n// splitDestinationAndPort takes an input string and returns the destination and port as a tuple, or an error if the input is invalid.\n// It supports two bracketed IPv6 forms:\n//   - \"[addr]:port\" (RFC 3986, e.g. \"[::1]:80\")\n//   - \"[addr]/prefix:port\" (e.g. \"[fd7a::1]/128:80,443\")\n//\n// Brackets are only accepted around IPv6 addresses, not IPv4, hostnames, or other alias types.\n// Bracket stripping reduces both forms to bare \"addr:port\" or \"addr/prefix:port\",\n// which the normal [strings.LastIndex] of \":\" split handles correctly because","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/utils.go#L1-L34","documentation":"ErrInputMissingColon is returned by splitDestinationAndPort (hscontrol/policy/v2/utils.go:67) when a destination-and-port string contains no ':' at all, so destination and port cannot be separated. Every ACL/grant destination must use the \"destination:port\" form (or the bracketed IPv6 forms \"[addr]:port\" / \"[addr]/prefix:port\"). It propagates out of destination parsing during policy load.","triggerScenarios":"A dst like \"tag:server\" — note the colon in \"tag:\" does not count only if... actually \"tag:server:22\" is fine; the error fires for inputs with zero colons such as \"10.0.0.1\", \"example-host\", \"autogroup:member\" (no port). Raised when strings.LastIndex(input, \":\") == -1.","commonSituations":"Forgetting the port suffix when writing dst entries; using a bare hostname where \"host:*\" was intended; YAML anchors or templating that strip the trailing \":*\" portion.","solutions":["Append a port to the destination: \":22\", \":80,443\", or \":*\"","For IPv6, use bracketed form \"[fd7a::1]:22\" (brackets are required, see ErrBracketsNotIPv6)","Check the erroring rule's dst array for any entry lacking a colon"],"exampleFix":"// before\n\"dst\": [\"tag:server\"]\n// after\n\"dst\": [\"tag:server:*\"]","handlingStrategy":"validation","validationCode":"// Check a dst string is splittable before adding to a policy\nfunc dstHasPort(dst string) bool {\n    i := strings.LastIndex(dst, \":\")\n    return i > 0 && i < len(dst)-1\n}","typeGuard":null,"tryCatchPattern":"if errors.Is(err, policyv2.ErrInputMissingColon) {\n    // append a port (\":*\" for any) to the destination\n}","preventionTips":["Always write destinations as destination:port","Use \":*\" when any port is intended","Lint policies for dst entries without a colon"],"tags":["policy","acl","parsing","ports"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}