{"record":{"id":"2b85884f09f6cd4a","repo":"tailscale/tailscale","slug":"missing-addr-parameter","errorCode":null,"errorMessage":"missing 'addr' parameter","messagePattern":"missing 'addr' parameter","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"ipn/localapi/localapi.go","lineNumber":589,"sourceCode":"\t\t\t\thttp.Error(w, \"invalid nodekey in 'addr' parameter\", http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tn, u, ok = b.WhoIsNodeKey(k)\n\t\t} else if ip, err := netip.ParseAddr(v); err == nil {\n\t\t\tipp = netip.AddrPortFrom(ip, 0)\n\t\t} else {\n\t\t\tvar err error\n\t\t\tipp, err = netip.ParseAddrPort(v)\n\t\t\tif err != nil {\n\t\t\t\thttp.Error(w, \"invalid 'addr' parameter\", http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tif ipp.IsValid() {\n\t\t\tn, u, ok = b.WhoIs(r.FormValue(\"proto\"), ipp)\n\t\t}\n\t} else {\n\t\thttp.Error(w, \"missing 'addr' parameter\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif !ok {\n\t\thttp.Error(w, \"no match for IP:port\", http.StatusNotFound)\n\t\treturn\n\t}\n\tres := &apitype.WhoIsResponse{\n\t\tNode:        n.AsStruct(), // always non-nil per WhoIsResponse contract\n\t\tUserProfile: &u,           // always non-nil per WhoIsResponse contract\n\t}\n\tif n.Addresses().Len() > 0 {\n\t\tsrc := n.Addresses().At(0).Addr()\n\t\tswitch {\n\t\tcase r.FormValue(\"svc_name\") != \"\":\n\t\t\tsvcName := tailcfg.AsServiceName(r.FormValue(\"svc_name\"))\n\t\t\tif svcName == \"\" {\n\t\t\t\thttp.Error(w, \"invalid svc_name\", http.StatusBadRequest)\n\t\t\t\treturn","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/ipn/localapi/localapi.go#L571-L607","documentation":"Returned as HTTP 400 by serveWhoIsWithBackend when the 'addr' query parameter is absent or empty (r.FormValue(\"addr\") == \"\"). addr is the only lookup key for whois, so the handler refuses immediately instead of guessing. Note an empty-but-present parameter (addr=) hits the same path.","triggerScenarios":"GET /localapi/v0/whois with no query string; calling with only other valid parameters (proto=, dst_ip=, svc_name=) but no addr; query-string construction bug that drops addr when the value is empty.","commonSituations":"Template code that conditionally appends parameters and skips addr when a variable is unset; curl invocations where the '?' or '&' got mangled; clients ported from an API that defaulted to 'self'.","solutions":["Always include addr, e.g. /localapi/v0/whois?addr=100.101.102.103.","Guard in the caller: if the address variable is empty, fail early with your own error instead of issuing the request.","Log the full URL when whois fails so missing parameters are obvious."],"exampleFix":"// before\nq := url.Values{}\nif ip != \"\" { q.Set(\"addr\", ip) } // ip empty -> request without addr -> 400\nres, _ := lc.DoLocalRequest(ctx, \"GET\", \"/localapi/v0/whois?\"+q.Encode(), nil)\n\n// after\nif ip == \"\" { return fmt.Errorf(\"whois: addr is required\") }\nq := url.Values{\"addr\": {ip}}\nres, _ := lc.DoLocalRequest(ctx, \"GET\", \"/localapi/v0/whois?\"+q.Encode(), nil)","handlingStrategy":"validation","validationCode":"if addr == \"\" {\n    return errors.New(\"whois: 'addr' parameter is required\")\n}\nq := url.Values{\"addr\": {addr}}\nres, err := lc.DoLocalRequest(ctx, \"GET\", \"/localapi/v0/whois?\"+q.Encode(), nil)","typeGuard":null,"tryCatchPattern":"if res.StatusCode == http.StatusBadRequest {\n    if addr == \"\" { return errors.New(\"whois called without addr\") }\n    return fmt.Errorf(\"whois rejected addr %q\", addr)\n}","preventionTips":["Build query strings with url.Values so parameters are never silently dropped.","Reject empty lookup inputs at your own API boundary with a clearer message than the server's."],"tags":["tailscale","localapi","whois","http-400","missing-parameter"],"backgroundTag":"http-400-bad-request","analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}