{"record":{"id":"eeb55c6879c6cb15","repo":"netbirdio/netbird","slug":"invalid-local-address-w","errorCode":null,"errorMessage":"invalid local address: %w","messagePattern":"invalid local address: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/cmd/ssh.go","lineNumber":660,"sourceCode":"\n\tgo func() {\n\t\tif err := c.LocalPortForward(ctx, localAddr, remoteAddr); err != nil && !errors.Is(err, context.Canceled) {\n\t\t\tcmd.Printf(\"Local port forward error: %v\\n\", err)\n\t\t}\n\t}()\n\n\treturn nil\n}\n\n// parseAndStartRemoteForward parses and starts a remote port forward (-R)\nfunc parseAndStartRemoteForward(ctx context.Context, c *sshclient.Client, forward string, cmd *cobra.Command) error {\n\tremoteAddr, localAddr, err := parsePortForwardSpec(forward)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := validateDestinationPort(localAddr); err != nil {\n\t\treturn fmt.Errorf(\"invalid local address: %w\", err)\n\t}\n\n\tlog.Debugf(\"Remote port forwarding: %s -> %s\", remoteAddr, localAddr)\n\n\tgo func() {\n\t\tif err := c.RemotePortForward(ctx, remoteAddr, localAddr); err != nil && !errors.Is(err, context.Canceled) {\n\t\t\tcmd.Printf(\"Remote port forward error: %v\\n\", err)\n\t\t}\n\t}()\n\n\treturn nil\n}\n\n// validateDestinationPort checks that the destination address has a valid port.\n// Port 0 is only valid for bind addresses (where the OS picks an available port),\n// not for destination addresses where we need to connect.\nfunc validateDestinationPort(addr string) error {\n\tif strings.HasPrefix(addr, \"/\") || strings.HasPrefix(addr, \"./\") {","sourceCodeStart":642,"sourceCodeEnd":678,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/cmd/ssh.go#L642-L678","documentation":"Wraps validateDestinationPort(localAddr) in the -R (remote forward) path. For remote forwards the spec is parsed as remoteAddr:localAddr and the validator is applied to the local address — the endpoint your machine will serve to the remote peer. It must be host:numeric-port (1-65535) or a unix socket path (leading / or ./).","triggerScenarios":"`netbird ssh -R 9000:localhost peer` (local side missing its port), `-R 9000:localhost:0`, `-R 9000:localhost:99999`, `-R 9000:svc:https` (non-numeric), or a local endpoint that fails net.SplitHostPort such as a bare unbracketed IPv6 address.","commonSituations":"Thinking -R ends at the remote bind port and omitting the local service port; environment-variable-built specs with an empty local port; pointing the local side at a socket but forgetting the leading / (so it is treated as a host with a bad port).","solutions":["Complete the spec with the local service's host and numeric port: -R 9000:127.0.0.1:8080.","Never use 0 for the local port — the remote peer dials a concrete endpoint.","Use only digits for the local port (no service names), 1-65535.","For unix socket targets, keep the absolute or ./-prefixed path so validation skips the port checks."],"exampleFix":"# before\nnetbird ssh -R 9000:localhost peer1\n# -> start port forwarding: remote port forward 9000:localhost: invalid local address: parse address localhost: missing port in address\n\n# after\nnetbird ssh -R 9000:127.0.0.1:8080 peer1","handlingStrategy":"validation","validationCode":"// pre-check the -R local endpoint (what the remote peer dials back to)\nfunc validateLocalTarget(addr string) error {\n\tif strings.HasPrefix(addr, \"/\") || strings.HasPrefix(addr, \"./\") {\n\t\treturn nil\n\t}\n\thost, portStr, err := net.SplitHostPort(addr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"local endpoint %q must be host:port\", addr)\n\t}\n\t_ = host\n\tp, err := strconv.Atoi(portStr)\n\tif err != nil || p < 1 || p > 65535 {\n\t\treturn fmt.Errorf(\"local port %q invalid (need 1-65535)\", portStr)\n\t}\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":"if err := validateDestinationPort(localAddr); err != nil {\n\treturn fmt.Errorf(\"invalid local address: %w\", err)\n\t// the validated side is the LOCAL endpoint even though the flag is -R;\n\t// read the wrapped cause to see if it is shape (SplitHostPort) or value (port)\n}","preventionTips":["Document in team runbooks: -R spec ends with the local host:port pair, fully explicit.","Reject empty/zero ports at config parse time in your own tooling (${LOCAL_PORT:?required}).","Prefer 127.0.0.1 over localhost for the local endpoint to avoid resolver surprises on exotic hosts.","Keep socket paths absolute so they take the exemption path instead of being parsed as host:port."],"tags":["go","ssh","port-forwarding","validation","cli"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}