juanfont/headscale · error
%s confirm form: no input tag for CSRF
Error message
%s confirm form: no input tag for CSRF
What it means
The parser found the CSRF field name (`name="headscale_register_confirm"`) but no `<input` tag opening before it in the preceding HTML. It walks backwards with `strings.LastIndex(before, "<input")` to capture the whole tag; failure means the attribute exists outside an input element (or the markup around it is not what the template normally emits).
Source
Thrown at integration/scenario.go:1277
actionStart := actionIdx + len(`action="`)
actionEnd := strings.Index(htmlBody[actionStart:], `"`)
if actionEnd == -1 {
return "", nil, fmt.Errorf("%s confirm form: unterminated action attribute", hostname) //nolint:err113
}
formAction := htmlBody[actionStart : actionStart+actionEnd]
// Extract hidden CSRF input value. The rendered <input> has
// attributes in name-type-value order so we grab the whole tag.
before, _, ok := strings.Cut(htmlBody, `name="headscale_register_confirm"`)
if !ok {
return "", nil, fmt.Errorf("%s confirm form: no CSRF input", hostname) //nolint:err113
}
tagStart := strings.LastIndex(before, "<input")
if tagStart == -1 {
return "", nil, fmt.Errorf("%s confirm form: no input tag for CSRF", hostname) //nolint:err113
}
tagEnd := strings.Index(htmlBody[tagStart:], ">")
if tagEnd == -1 {
return "", nil, fmt.Errorf("%s confirm form: unterminated input tag", hostname) //nolint:err113
}
inputTag := htmlBody[tagStart : tagStart+tagEnd+1]
valIdx := strings.Index(inputTag, `value="`)
if valIdx == -1 {
return "", nil, fmt.Errorf("%s confirm form: no value in CSRF input", hostname) //nolint:err113
}
valStart := valIdx + len(`value="`)
valEnd := strings.Index(inputTag[valStart:], `"`)
csrfToken := inputTag[valStart : valStart+valEnd]
View on GitHub (pinned to 565fd254d0)
Solutions
- Inspect the HTML preceding the CSRF field name to see what element actually carries it.
- Make the search case-insensitive (`strings.LastIndex(strings.ToLower(before), "<input")`) if only casing changed.
- Switch to x/net/html form parsing so element structure is handled properly.
Defensive patterns
Strategy: validation
Prevention
- Prefer an HTML parser; tag-structure questions disappear.
- If scraping, match case-insensitively on both tag and attribute names.
- Pin template snapshots in tests so markup drift is caught at PR time.
When it happens
Trigger: Produced when the literal `name="headscale_register_confirm"` appears in the body but the text before it contains no `<input` — e.g. the name is rendered inside a <label>, a script string, or the tag is emitted as `<INPUT` with different casing.
Common situations: Template refactor moved the hidden input into a partial that renders different markup; attribute appears in an error message echoed to the page; case change in the tag name.
Related errors
- %s confirm form: no CSRF input
- %s confirm form: unterminated input tag
- %s confirm form: no value in CSRF input
- tag should be lowercase
- %s confirm form: unterminated action attribute
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/16b1517af6a934e9.
Report an issue: GitHub.