crowdsecurity/crowdsec · error

parse challenge html template: %w

Error message

parse challenge html template: %w

What it means

NewChallengeRuntime parses the built-in challenge HTML/JS template with text/template once at startup and wraps parse failures with 'parse challenge html template'. Since htmlTemplate is hardcoded in the source, this only fires if the template text was modified and became syntactically invalid ({{ }} imbalance, bad actions).

Source

Thrown at pkg/appsec/challenge/challenge.go:465

	}

	// No need to keep the closer around, we can just close the runtime itself when stopping
	if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil {
		return nil, fmt.Errorf("failed to instantiate WASI: %w", err)
	}

	compiledMod, err := compileObfuscatorModule(ctx, r)
	if err != nil {
		return nil, err
	}

	// We use text/template instead of html/template because the data we send
	// is pretty much hardcoded and trusted; html/template would escape the JS
	// we inject. Parsed once here so GetChallengePage doesn't re-parse on
	// every request.
	htmlTpl, err := template.New("challenge").Parse(htmlTemplate)
	if err != nil {
		return nil, fmt.Errorf("parse challenge html template: %w", err)
	}

	challengeRuntime := &ChallengeRuntime{
		r:                  r,
		obfuscatorMod:      compiledMod,
		powDifficulty:      defaultPowDifficulty,
		keys:               keys,
		cryptoPoolSize:     cryptoPoolSize,
		dynamicModuleCache: make(map[int64][]string),
		cookieTTL:          cookieTTL,
		maxCookieLen:       maxCookieLen,
		htmlTpl:            htmlTpl,
		spent:              newSpentSet(spentSetMaxEntries),
		logger:             logger,
	}

	// Load the build-time-obfuscated challenge code from the baked-in bundle so
	// we can serve immediately.

View on GitHub (pinned to 909b515798)

Solutions

  1. Revert htmlTemplate in challenge.go to the upstream version (git checkout -- pkg/appsec/challenge/challenge.go).
  2. If customizing, validate the template offline: template.Must(template.New("challenge").Parse(yourHTML)) in a scratch test.
  3. Escape literal braces in JS/CSS with {{"{"}} or use a custom delimiter set ({{ "{" }} or template.New(...).Delims).
  4. Run go test ./pkg/appsec/challenge/... to catch parse failures at build time.

Example fix

// before (invalid literal braces in JS)
for (let i = 0; i < {{.MaxIter}}; i++) { hash() }
// after
for (let i = 0; i < {{.MaxIter}}; i++) {{"{"}} hash() {{"}"}}
Defensive patterns

Strategy: validation

Validate before calling

// Validate template parseability at build/test time
_, err := template.New("challenge").Parse(htmlTemplate)
if err != nil {
    return fmt.Errorf("embedded challenge template invalid: %w", err)
}

Try / catch

htmlTpl, err := template.New("challenge").Parse(htmlTemplate)
if err != nil { return nil, fmt.Errorf("parse challenge html template: %w", err) } // the wrapped error gives line/col of the bad action

Prevention

When it happens

Trigger: NewChallengeRuntime -> template.New("challenge").Parse(htmlTemplate) fails: the embedded htmlTemplate constant was edited (fork, patch, generated variant) and contains invalid template syntax.

Common situations: Rebranding/customizing the challenge page in a fork with mismatched {{...}} delimiters; code-generation mistakes altering the template string.

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/bc34b2060e974bb7. Report an issue: GitHub.