{"record":{"id":"9d1b9dcb3ddefa9e","repo":"juanfont/headscale","slug":"invalid-first-integer","errorCode":null,"errorMessage":"invalid first integer","messagePattern":"invalid first integer","errorType":"validation","errorClass":"ErrInvalidPortNumber","httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/utils.go","lineNumber":22,"sourceCode":"\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\n// port strings never contain colons.\nfunc splitDestinationAndPort(input string) (string, string, error) {\n\t// Handle RFC 3986 bracketed IPv6 (e.g. \"[::1]:80\" or \"[fd7a::1]/128:80,443\").\n\t// Strip brackets after validation and fall through to normal parsing.\n\tif strings.HasPrefix(input, \"[\") {\n\t\tcloseBracket := strings.Index(input, \"]\")","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/utils.go#L4-L40","documentation":"ErrInvalidPortNumber is returned by parsePort (hscontrol/policy/v2/utils.go:142) when a port token is not a parseable integer at all — strconv.Atoi fails. This catches service names (\"http\", \"ssh\"), alphabetic strings, trailing units (\"8080tcp\"), and other non-numeric garbage in the port position. Note the sentinel text says 'invalid first integer' but it applies to any port token, first or last.","triggerScenarios":"Dst port sections like \"host:http\", \"host:ssh-443\", or \"host:8o\" (letter o). Raised when strconv.Atoi(portStr) errors inside parsePort, reached for single ports and both range endpoints.","commonSituations":"Writing IANA service names instead of numbers (headscale requires numeric ports); OCR/typing errors (letter l vs 1, O vs 0); copy-pasting from docs that use names; localized keyboards producing odd digits.","solutions":["Replace the service name with its numeric port (http -> 80, ssh -> 22, https -> 443)","Remove any non-numeric suffixes/characters from the token","Check range endpoints too — \"80-https\" fails with the same error"],"exampleFix":"// before\n\"dst\": [\"tag:web:http\"]\n// after\n\"dst\": [\"tag:web:80\"]","handlingStrategy":"validation","validationCode":"func portNumeric(tok string) bool {\n    for _, p := range strings.Split(tok, \",\") {\n        for _, q := range strings.Split(p, \"-\") {\n            if q == \"\" || q == \"*\" { continue }\n            if _, err := strconv.Atoi(q); err != nil { return false }\n        }\n    }\n    return true\n}","typeGuard":null,"tryCatchPattern":"if errors.Is(err, policyv2.ErrInvalidPortNumber) {\n    // replace service names (http/ssh) with numeric ports; strip stray characters\n}","preventionTips":["headscale takes numeric ports only — keep an IANA port map handy","Watch for letter/digit confusion (O vs 0, l vs 1)","Validate both endpoints of ranges"],"tags":["policy","acl","parsing","ports"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}