juanfont/headscale · error

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).

Solutions

  1. Inspect the HTML preceding the CSRF field name to see what element actually carries it.
  2. Make the search case-insensitive (`strings.LastIndex(strings.ToLower(before), "<input")`) if only casing changed.
  3. Switch to x/net/html form parsing so element structure is handled properly.
Defensive patterns

Strategy: validation

Prevention

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


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/16b1517af6a934e9. Report an issue: GitHub.

Appendix: 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)