{"record":{"id":"bc34b2060e974bb7","repo":"crowdsecurity/crowdsec","slug":"parse-challenge-html-template-w","errorCode":null,"errorMessage":"parse challenge html template: %w","messagePattern":"parse challenge html template: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/appsec/challenge/challenge.go","lineNumber":465,"sourceCode":"\t}\n\n\t// No need to keep the closer around, we can just close the runtime itself when stopping\n\tif _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to instantiate WASI: %w\", err)\n\t}\n\n\tcompiledMod, err := compileObfuscatorModule(ctx, r)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// We use text/template instead of html/template because the data we send\n\t// is pretty much hardcoded and trusted; html/template would escape the JS\n\t// we inject. Parsed once here so GetChallengePage doesn't re-parse on\n\t// every request.\n\thtmlTpl, err := template.New(\"challenge\").Parse(htmlTemplate)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"parse challenge html template: %w\", err)\n\t}\n\n\tchallengeRuntime := &ChallengeRuntime{\n\t\tr:                  r,\n\t\tobfuscatorMod:      compiledMod,\n\t\tpowDifficulty:      defaultPowDifficulty,\n\t\tkeys:               keys,\n\t\tcryptoPoolSize:     cryptoPoolSize,\n\t\tdynamicModuleCache: make(map[int64][]string),\n\t\tcookieTTL:          cookieTTL,\n\t\tmaxCookieLen:       maxCookieLen,\n\t\thtmlTpl:            htmlTpl,\n\t\tspent:              newSpentSet(spentSetMaxEntries),\n\t\tlogger:             logger,\n\t}\n\n\t// Load the build-time-obfuscated challenge code from the baked-in bundle so\n\t// we can serve immediately.","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/appsec/challenge/challenge.go#L447-L483","documentation":"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).","triggerScenarios":"NewChallengeRuntime -> template.New(\"challenge\").Parse(htmlTemplate) fails: the embedded htmlTemplate constant was edited (fork, patch, generated variant) and contains invalid template syntax.","commonSituations":"Rebranding/customizing the challenge page in a fork with mismatched {{...}} delimiters; code-generation mistakes altering the template string.","solutions":["Revert htmlTemplate in challenge.go to the upstream version (git checkout -- pkg/appsec/challenge/challenge.go).","If customizing, validate the template offline: template.Must(template.New(\"challenge\").Parse(yourHTML)) in a scratch test.","Escape literal braces in JS/CSS with {{\"{\"}} or use a custom delimiter set ({{ \"{\" }} or template.New(...).Delims).","Run go test ./pkg/appsec/challenge/... to catch parse failures at build time."],"exampleFix":"// before (invalid literal braces in JS)\nfor (let i = 0; i < {{.MaxIter}}; i++) { hash() }\n// after\nfor (let i = 0; i < {{.MaxIter}}; i++) {{\"{\"}} hash() {{\"}\"}}","handlingStrategy":"validation","validationCode":"// Validate template parseability at build/test time\n_, err := template.New(\"challenge\").Parse(htmlTemplate)\nif err != nil {\n    return fmt.Errorf(\"embedded challenge template invalid: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"htmlTpl, err := template.New(\"challenge\").Parse(htmlTemplate)\nif err != nil { return nil, fmt.Errorf(\"parse challenge html template: %w\", err) } // the wrapped error gives line/col of the bad action","preventionTips":["Escape literal braces in embedded JS/CSS with {{\"{\"}} / {{\"}\"}}","Add a parse test for the embedded template so edits fail in CI","Use template.Must for compile-time safety in fork variants"],"tags":["go","templates","startup","embedded-assets"],"backgroundTag":"invalid-regex-pattern","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}