{"record":{"id":"0eb99653a3b5ead5","repo":"juanfont/headscale","slug":"s-confirm-form-no-csrf-input","errorCode":null,"errorMessage":"%s confirm form: no CSRF input","messagePattern":"(.+?) confirm form: no CSRF input","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"integration/scenario.go","lineNumber":1272,"sourceCode":"\tactionIdx := strings.Index(htmlBody, `action=\"`)\n\tif actionIdx == -1 {\n\t\treturn \"\", nil, fmt.Errorf(\"%s confirm form: no action attribute\", hostname) //nolint:err113\n\t}\n\n\tactionStart := actionIdx + len(`action=\"`)\n\n\tactionEnd := strings.Index(htmlBody[actionStart:], `\"`)\n\tif actionEnd == -1 {\n\t\treturn \"\", nil, fmt.Errorf(\"%s confirm form: unterminated action attribute\", hostname) //nolint:err113\n\t}\n\n\tformAction := htmlBody[actionStart : actionStart+actionEnd]\n\n\t// Extract hidden CSRF input value. The rendered <input> has\n\t// attributes in name-type-value order so we grab the whole tag.\n\tbefore, _, ok := strings.Cut(htmlBody, `name=\"headscale_register_confirm\"`)\n\tif !ok {\n\t\treturn \"\", nil, fmt.Errorf(\"%s confirm form: no CSRF input\", hostname) //nolint:err113\n\t}\n\n\ttagStart := strings.LastIndex(before, \"<input\")\n\tif tagStart == -1 {\n\t\treturn \"\", nil, fmt.Errorf(\"%s confirm form: no input tag for CSRF\", hostname) //nolint:err113\n\t}\n\n\ttagEnd := strings.Index(htmlBody[tagStart:], \">\")\n\tif tagEnd == -1 {\n\t\treturn \"\", nil, fmt.Errorf(\"%s confirm form: unterminated input tag\", hostname) //nolint:err113\n\t}\n\n\tinputTag := htmlBody[tagStart : tagStart+tagEnd+1]\n\n\tvalIdx := strings.Index(inputTag, `value=\"`)\n\tif valIdx == -1 {\n\t\treturn \"\", nil, fmt.Errorf(\"%s confirm form: no value in CSRF input\", hostname) //nolint:err113\n\t}","sourceCodeStart":1254,"sourceCodeEnd":1290,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/integration/scenario.go#L1254-L1290","documentation":"The confirm-form parser could not find the hidden CSRF input `name=\"headscale_register_confirm\"` anywhere in the HTML body. The integration Scenario auto-submits the registration confirm form and needs this hidden field's value to POST; its absence means the page returned is not the expected confirm form.","triggerScenarios":"The helper runs `strings.Cut(htmlBody, \"name=\\\"headscale_register_confirm\\\"\")` after a successful GET of the confirm page and `ok` is false. Produced when the server returned a login/error/redirect page instead of the confirm form, or when the template renamed the field.","commonSituations":"Auth flow changed (OIDC interposed, session cookie missing so an intermediate page renders); the hidden input's attribute order or quoting changed in the template; the registration key was already consumed so a 'node already registered' page is returned.","solutions":["Log or dump `htmlBody` when this fails and confirm which page was actually served.","Ensure the HTTP client carries the cookies/redirect history from the registration flow so the confirm form is reached.","If the template was updated, align the literal `name=\"headscale_register_confirm\"` in integration/scenario.go with the new field name.","Check that the registration key/auth-id is still valid at this point in the flow."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"want := `name=\"headscale_register_confirm\"`\nif !strings.Contains(htmlBody, want) {\n    log.Printf(\"%s unexpected confirm page (first 500 bytes): %.500s\", hostname, htmlBody)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reuse the same cookie-bearing http.Client for the GET and the POST so the session matches.","Dump the body on mismatch — nine times out of ten it is a different page than assumed.","Rename the constant in one place shared by template and parser."],"tags":["html-parsing","integration","csrf","registration"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}