{"record":{"id":"5cd2552ce2fd7d05","repo":"juanfont/headscale","slug":"empty-auth-key-in-response","errorCode":null,"errorMessage":"empty auth key in response","messagePattern":"empty auth key in response","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"cmd/dev/main.go","lineNumber":29,"sourceCode":"\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"syscall\"\n\t\"time\"\n)\n\nvar (\n\tport = flag.Int(\"port\", 8080, \"headscale listen port\")\n\tkeep = flag.Bool(\"keep\", false, \"keep state directory on exit\")\n)\n\nvar errHealthTimeout = errors.New(\"health check timed out\")\n\nvar errEmptyAuthKey = errors.New(\"empty auth key in response\")\n\n// maxDevPort is the highest --port value that keeps the derived metrics\n// port (port+1010) inside the valid 1..65535 TCP range.\nconst maxDevPort = 64525\n\nconst devConfig = `---\nserver_url: http://127.0.0.1:%d\nlisten_addr: 127.0.0.1:%d\nmetrics_listen_addr: 127.0.0.1:%d\n\nnoise:\n  private_key_path: %s/noise_private.key\n\nprefixes:\n  v4: 100.64.0.0/10\n  v6: fd7a:115c:a1e0::/48\n  allocation: sequential\n","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/cmd/dev/main.go#L11-L47","documentation":"Returned as HTTP 400 by the POST branch of the /debug/ping handler when r.ParseForm() fails after the body has been wrapped in a MaxBytesReader limited to 4096 bytes. ParseForm fails on malformed URL-encoded bodies, invalid Content-Type form encodings, or when the body exceeds the 4 KiB cap (MaxBytesReader then makes ParseForm return an error).","triggerScenarios":"POSTing to /debug/ping with a body larger than 4096 bytes; sending a form with broken URL-encoding (e.g. stray % sequences); POSTing with Content-Type application/x-www-form-urlencoded but non-form payloads such as raw JSON; truncated/chunked request bodies.","commonSituations":"Scripts that POST JSON instead of form fields; pasting a huge node list or long node FQDN into the ping form; a proxy that mangles or truncates the body; automated clients that omit Content-Type.","solutions":["Send a small, correctly URL-encoded form: curl -d 'node=machine-name' (under 4096 bytes) with Content-Type application/x-www-form-urlencoded.","If the node identifier is short, use the GET variant instead: /debug/ping?node=<name>, which skips form parsing entirely.","Verify no proxy in front of headscale is inflating or corrupting the request body.","Check for accidental multipart or JSON bodies; this handler only reads r.FormValue(\"node\")."],"exampleFix":"# before (400 bad form data)\ncurl -X POST http://host/debug/ping -H 'Content-Type: application/json' -d '{\"node\":\"big-machine\"}'\n\n# after\ncurl -X POST http://host/debug/ping -d 'node=big-machine'\n# or simply\ncurl 'http://host/debug/ping?node=big-machine'","handlingStrategy":"validation","validationCode":"// Prefer the GET form; if POSTing, keep the body a tiny url-encoded form.\nif len(nodeQuery) > 4000 { // handler caps at 4096\n    return fmt.Errorf(\"node query too large for /debug/ping\")\n}\nvalues := url.Values{\"node\": {nodeQuery}}\nreq, _ := http.NewRequest(http.MethodPost, debugURL+\"/ping\", strings.NewReader(values.Encode()))\nreq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")","typeGuard":null,"tryCatchPattern":"resp, err := http.DefaultClient.Do(req)\nif err == nil && resp.StatusCode == http.StatusBadRequest {\n    // fall back to the GET variant which never parses a form\n    resp2, _ := http.Get(debugURL + \"/ping?node=\" + url.QueryEscape(nodeQuery))\n    defer resp2.Body.Close()\n}","preventionTips":["Send node as a small application/x-www-form-urlencoded body, never JSON, to this handler.","Keep payloads well under the 4096-byte MaxBytesReader cap.","Prefer /debug/ping?node=... GET links when scripting."],"tags":["http","debug","form-parsing","payload-limit"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}