{"record":{"id":"23510d3e7848a7fa","repo":"juanfont/headscale","slug":"hostport-must-contain-a-colon","errorCode":null,"errorMessage":"hostport must contain a colon","messagePattern":"hostport must contain a colon","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":118,"sourceCode":"// would advertise a feature that does not work. Reject at policy load and\n// point operators at the issue.\nvar nodeAttrUnsupportedCaps = map[tailcfg.NodeCapability]string{\n\ttailcfg.NodeAttrFunnel: \"https://github.com/juanfont/headscale/issues/2527\",\n}\n\n// Policy validation errors.\nvar (\n\tErrInvalidUsername             = errors.New(\"username must contain @\")\n\tErrUserNotFound                = errors.New(\"user not found\")\n\tErrMultipleUsersFound          = errors.New(\"multiple users found\")\n\tErrInvalidGroupFormat          = errors.New(\"group must start with 'group:'\")\n\tErrInvalidTagFormat            = errors.New(\"tag must start with 'tag:'\")\n\tErrInvalidHostname             = errors.New(\"invalid hostname\")\n\tErrHostResolve                 = errors.New(\"error resolving host\")\n\tErrInvalidPrefix               = errors.New(\"invalid prefix\")\n\tErrInvalidAutogroup            = errors.New(\"invalid autogroup\")\n\tErrUnknownAutogroup            = errors.New(\"unknown autogroup\")\n\tErrHostportMissingColon        = errors.New(\"hostport must contain a colon\")\n\tErrTypeNotSupported            = errors.New(\"type not supported\")\n\tErrInvalidAlias                = errors.New(\"invalid alias format\")\n\tErrInvalidAutoApprover         = errors.New(\"invalid auto approver format\")\n\tErrInvalidOwner                = errors.New(\"invalid owner format\")\n\tErrGroupNotDefined             = errors.New(\"group not defined in policy\")\n\tErrInvalidGroupMember          = errors.New(\"invalid group member type\")\n\tErrGroupValueNotArray          = errors.New(\"group value must be an array of users\")\n\tErrInvalidHostIP               = errors.New(\"hostname contains invalid IP address\")\n\tErrTagNotDefined               = errors.New(\"tag not found\")\n\tErrAutoApproverNotAlias        = errors.New(\"auto approver is not an alias\")\n\tErrInvalidACLAction            = errors.New(\"invalid ACL action\")\n\tErrInvalidSSHAction            = errors.New(\"invalid SSH action\")\n\tErrInvalidProtocolNumber       = errors.New(\"invalid protocol number\")\n\tErrProtocolLeadingZero         = errors.New(\"leading 0 not permitted in protocol number\")\n\tErrProtocolOutOfRange          = errors.New(\"protocol number out of range (0-255)\")\n\tErrAutogroupNotSupported       = errors.New(\"autogroup not supported in headscale\")\n\tErrAutogroupInternetSrc        = errors.New(\"autogroup:internet can only be used in ACL destinations\")\n\tErrAutogroupSelfSrc            = errors.New(\"\\\"autogroup:self\\\" not valid on the src side of a rule\")","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L100-L136","documentation":"Hostport parsing (hscontrol/policy/v2/types.go:899) requires a host:port token to contain a colon separating host and port(s). ACL dst entries are strings like 'host:80,443'; a token with no colon cannot be split and is rejected with this sentinel at parse time.","triggerScenarios":"Writing a dst entry as just the hostname/IP (\"example-host\") or just a port (\"443\"), forgetting the ':' separator; also 'example-host-443' with a dash instead of colon. The '*' wildcard alone is accepted; the colon-less non-wildcard is not.","commonSituations":"First ACL authoring: omitting ports because you meant 'all ports' (use 'host:*' instead); copy-paste from firewall rules that use space or dash separators.","solutions":["Write dst entries as 'host:ports', e.g. 'example-host:80,443'","Use 'host:*' to mean all ports","Check every dst string in the ACL/grant for the missing colon"],"exampleFix":"// before\n{\"acls\": [{\"action\": \"accept\", \"src\": [\"group:admins\"], \"dst\": [\"example-host\"]}]}\n// after\n{\"acls\": [{\"action\": \"accept\", \"src\": [\"group:admins\"], \"dst\": [\"example-host:*\"]}]}","handlingStrategy":"validation","validationCode":"for _, dst := range rule.Dst {\n    if dst != \"*\" && !strings.Contains(dst, \":\") {\n        return fmt.Errorf(\"dst %q must be host:port(s)\", dst)\n    }\n}","typeGuard":"func isHostport(s string) bool { return s == \"*\" || strings.Contains(s, \":\") }","tryCatchPattern":"if errors.Is(err, policy.ErrHostportMissingColon) {\n    // rewrite as 'host:*' or 'host:port'\n}","preventionTips":["Always write dst as host:ports","Use host:* for all ports","Lint dst entries in CI"],"tags":["headscale","policy","acl","hostport","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}