{"record":{"id":"7b5f491aebfd12c1","repo":"juanfont/headscale","slug":"request-body-too-large","errorCode":null,"errorMessage":"request body too large","messagePattern":"request body too large","errorType":"http","errorClass":"HTTPError","httpStatus":413,"severity":"warning","filePath":"hscontrol/handlers.go","lineNumber":132,"sourceCode":"\t\treturn 0, NewHTTPError(http.StatusBadRequest, \"invalid capability version\", fmt.Errorf(\"parsing capability version: %w\", err))\n\t}\n\n\treturn tailcfg.CapabilityVersion(clientCapabilityVersion), nil\n}\n\n// verifyBodyLimit caps the request body for /verify. The DERP verify\n// protocol payload ([tailcfg.DERPAdmitClientRequest]) is a few hundred\n// bytes; 4 KiB is generous and prevents an unauthenticated client from\n// OOMing the public router with arbitrarily large POSTs.\nconst verifyBodyLimit int64 = 4 * 1024\n\nfunc (h *Headscale) handleVerifyRequest(\n\treq *http.Request,\n\twriter io.Writer,\n) error {\n\tbody, err := io.ReadAll(req.Body)\n\tif err != nil {\n\t\treturn NewHTTPError(http.StatusRequestEntityTooLarge, \"request body too large\", fmt.Errorf(\"reading request body: %w\", err))\n\t}\n\n\tvar derpAdmitClientRequest tailcfg.DERPAdmitClientRequest\n\tif err := json.Unmarshal(body, &derpAdmitClientRequest); err != nil { //nolint:noinlineerr\n\t\treturn NewHTTPError(http.StatusBadRequest, \"Bad Request: invalid JSON\", fmt.Errorf(\"parsing DERP client request: %w\", err))\n\t}\n\n\tallow := h.state.ListNodes().ContainsFunc(func(n types.NodeView) bool {\n\t\treturn n.NodeKey() == derpAdmitClientRequest.NodePublic\n\t})\n\n\tresp := &tailcfg.DERPAdmitClientResponse{\n\t\tAllow: allow,\n\t}\n\n\treturn json.NewEncoder(writer).Encode(resp)\n}\n","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/handlers.go#L114-L150","documentation":"Returned by handleVerifyRequest when io.ReadAll on the POST body of the /verify endpoint (DERP verify protocol) fails. The 413 status reflects that the request body exceeded the limit enforced via verifyBodyLimit (4 KiB) or that the connection was interrupted mid-read. The DERP router calls /verify to check whether a node key is allowed to connect to a relay.","triggerScenarios":"A DERP server POSTing a DERPAdmitClientRequest larger than the body limit to /verify, or a client aborting the connection while the body is being read.","commonSituations":"A misconfigured or malicious actor POSTing large payloads to the public /verify endpoint; network interruptions between DERP router and headscale; a proxy that buffers and re-sends oversized bodies.","solutions":["Confirm the sender is a real DERP router sending a DERPAdmitClientRequest (a few hundred bytes)","Check for intermediary proxies that might inflate or duplicate the request body","If legitimate payloads exceed 4 KiB, review verifyBodyLimit in hscontrol/handlers.go and raise it deliberately"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Client side: keep the DERPAdmitClientRequest tiny and marshal it fully before sending\npayload, err := json.Marshal(&tailcfg.DERPAdmitClientRequest{NodePublic: nodeKey})\nif err != nil {\n    return err\n}\nif len(payload) > 4*1024 {\n    return fmt.Errorf(\"verify payload exceeds 4 KiB limit\")\n}","typeGuard":null,"tryCatchPattern":"resp, err := client.Post(verifyURL, \"application/json\", bytes.NewReader(payload))\nif err != nil {\n    return err\n}\nif resp.StatusCode == http.StatusRequestEntityTooLarge {\n    // body exceeded the server's verifyBodyLimit; shrink or inspect the payload\n}","preventionTips":["Never send anything but a marshalled DERPAdmitClientRequest to /verify","Monitor 413s on /verify as a signal of misuse or a broken intermediary"],"tags":["derp","http","body-limit","dos-protection"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}