{"record":{"id":"67a1f23f33ec4fbb","repo":"netbirdio/netbird","slug":"invalid-port-forward-specification-s","errorCode":null,"errorMessage":"invalid port forward specification: %s","messagePattern":"invalid port forward specification: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/cmd/ssh.go","lineNumber":730,"sourceCode":"\n\tif strings.HasPrefix(spec, \"[\") && strings.Contains(spec, \"]:\") {\n\t\treturn parseIPv6ForwardSpec(spec)\n\t}\n\n\tparts := strings.Split(spec, \":\")\n\tif len(parts) < 2 {\n\t\treturn \"\", \"\", fmt.Errorf(\"invalid port forward specification: %s (expected format: [local_host:]local_port:remote_target)\", spec)\n\t}\n\n\tswitch len(parts) {\n\tcase 2:\n\t\treturn parseTwoPartForwardSpec(parts, spec)\n\tcase 3:\n\t\treturn parseThreePartForwardSpec(parts)\n\tcase 4:\n\t\treturn parseFourPartForwardSpec(parts)\n\tdefault:\n\t\treturn \"\", \"\", fmt.Errorf(\"invalid port forward specification: %s\", spec)\n\t}\n}\n\n// parseTwoPartForwardSpec handles \"port:unix_socket\" format.\nfunc parseTwoPartForwardSpec(parts []string, spec string) (string, string, error) {\n\tif isUnixSocket(parts[1]) {\n\t\tlocalAddr := \"localhost:\" + parts[0]\n\t\tremoteAddr := parts[1]\n\t\treturn localAddr, remoteAddr, nil\n\t}\n\treturn \"\", \"\", fmt.Errorf(\"invalid port forward specification: %s (expected format: [local_host:]local_port:remote_host:remote_port or [local_host:]local_port:unix_socket)\", spec)\n}\n\n// parseThreePartForwardSpec handles \"port:host:hostport\" or \"host:port:unix_socket\" formats.\nfunc parseThreePartForwardSpec(parts []string) (string, string, error) {\n\tif isUnixSocket(parts[2]) {\n\t\tlocalHost := normalizeLocalHost(parts[0])\n\t\tlocalAddr := localHost + \":\" + parts[1]","sourceCodeStart":712,"sourceCodeEnd":748,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/cmd/ssh.go#L712-L748","documentation":"Returned by parsePortForwardSpec's default branch when the spec splits into more than four colon-separated parts. Valid specs have 2, 3, or 4 parts (port:socket, port:host:port / host:port:socket, host:port:host:port); five or more tokens have no handler. IPv6-looking specs with many colons land here unless they start with '[' and contain ']:' to be routed to the IPv6 parser.","triggerScenarios":"`-L 8080:host:80:extra` (5 parts), unbracketed IPv6 with ports like `-L ::1:8080:host:80` (6 parts and no leading '['), or specs pasted with a trailing `:`. The '[...]' guard earlier only diverts specs that both start with '[' and contain ']:' — anything else with >4 parts hits this branch.","commonSituations":"Adding an extra hop or protocol suffix by mistake; unbracketed IPv6 addresses (the classic — every v6 literal adds 7+ colons); appending a trailing separator in generated config.","solutions":["Reduce to at most 4 parts: [local_host:]local_port:remote_host:remote_port.","Bracket IPv6 local hosts so the spec is routed to parseIPv6ForwardSpec: -L [::1]:8080:host:80.","Drop trailing colons and extra segments introduced by copy-paste or templating."],"exampleFix":"# before\nnetbird ssh -L ::1:8080:host:80 peer1\n# -> invalid port forward specification: ::1:8080:host:80\n\n# after\nnetbird ssh -L '[::1]:8080:host:80' peer1","handlingStrategy":"validation","validationCode":"n := strings.Count(spec, \":\") // +1 = parts count; require 2..4 parts\nif n < 1 || n > 3 {\n\tif strings.Count(spec, \":\") >= 3 && strings.HasPrefix(spec, \"[\") && !strings.Contains(spec, \"]:\") {\n\t\treturn fmt.Errorf(\"IPv6 spec %q must be written as [v6]:port:host:hostport\", spec)\n\t}\n\treturn fmt.Errorf(\"spec %q must have 2-4 colon parts\", spec)\n}","typeGuard":"func isWellFormedForwardSpec(s string) bool {\n\tif strings.HasPrefix(s, \"[\") {\n\t\treturn strings.Contains(s, \"]:\")\n\t}\n\tn := strings.Count(s, \":\")\n\treturn n >= 1 && n <= 3 // 2-4 parts\n}","tryCatchPattern":"if len(parts) > 4 {\n\t// over-segmented: usually an unbracketed IPv6 or a trailing ':' —\n\t// re-normalize (bracket v6, trim separators) and retry once, else reject\n}","preventionTips":["Bracket IPv6 hosts at the moment of construction, not later — most >4-part failures are v6 literals.","Trim trailing colons from template output (spec.TrimSuffix(\":\")).","Keep a regexp lint in CI for forward flags: ^\\[?[0-9a-zA-Z.:%/_-]+\\]?:... with bounded part count.","Prefer names or IPv4 for remote targets; the 4-part grammar has no slot for a second v6 literal."],"tags":["go","ssh","port-forwarding","validation","ipv6"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}