{"record":{"id":"3173355eec543f1b","repo":"crowdsecurity/crowdsec","slug":"build-dynamic-key-module-w","errorCode":null,"errorMessage":"build dynamic key module: %w","messagePattern":"build dynamic key module: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/appsec/challenge/challenge.go","lineNumber":585,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"generate PoW salt: %w\", err)\n\t}\n\tpowMAC := c.computePowMAC(powSalt, r, ts, difficulty)\n\n\tif c.log().Logger.IsLevelEnabled(log.DebugLevel) {\n\t\tissEpoch, issKey := c.keys.Current()\n\t\tc.log().WithFields(log.Fields{\n\t\t\t\"r\":          r,\n\t\t\t\"epoch\":      issEpoch,\n\t\t\t\"k_epoch\":    fmt.Sprintf(\"%x\", issKey),\n\t\t\t\"difficulty\": difficulty,\n\t\t}).Debug(\"issued challenge\")\n\t}\n\n\t// The challenge code only carries the hook registration; the dynamic module\n\t// carries the per-epoch K, so K never appears in plain HTML.\n\tdynamicModule, err := c.currentDynamicModule(ctx)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"build dynamic key module: %w\", err)\n\t}\n\n\tvar renderedPage strings.Builder\n\n\tif err := c.htmlTpl.Execute(&renderedPage, map[string]interface{}{\n\t\t\"JSChallenge\":   challengeCode,\n\t\t\"DynamicModule\": dynamicModule,\n\t\t\"FPScannerPath\": ChallengeFPScannerPath,\n\t\t\"PowDifficulty\": difficulty,\n\t\t\"PowPrefix\":     powSalt,\n\t\t\"PowMAC\":        powMAC,\n\t\t\"Timestamp\":     ts,\n\t\t\"R\":             r,\n\t}); err != nil {\n\t\treturn \"\", fmt.Errorf(\"render challenge page: %w\", err)\n\t}\n\treturn renderedPage.String(), nil\n}","sourceCodeStart":567,"sourceCodeEnd":603,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/appsec/challenge/challenge.go#L567-L603","documentation":"The challenge page combines a static hook-registration snippet with a dynamic key module carrying the per-epoch key K. GetChallengePage fetches the current dynamic module via currentDynamicModule(ctx); failure is wrapped as 'build dynamic key module'. Without it the page cannot deliver the epoch key and the challenge is unbuildable.","triggerScenarios":"Calling GetChallengePage when the cache is empty (pre-warm not done/failed) and currentDynamicModule must obfuscate synchronously, which then fails (ctx cancelled/deadline exceeded, generation error).","commonSituations":"First request after startup with skipPreWarm=true and a short request timeout; pre-warmer goroutine stopped (Close called or panic); obfuscation slower than the request deadline under load.","solutions":["Ensure skipPreWarm is false so the module is pre-warmed at startup.","Increase the request/HTTP server timeout to tolerate a synchronous ~5s obfuscation.","Check logs for pre-warmer failures — fix the underlying obfuscation/generation error.","Restart the runtime if the pre-warmer goroutine was stopped without a reload."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"page, err := rt.GetChallengePage(ctx, opts)\nif err != nil {\n    if strings.Contains(err.Error(), \"build dynamic key module\") {\n        logger.WithError(err).Warn(\"dynamic module unavailable; serving static challenge only\")\n    }\n}","preventionTips":["Keep skipPreWarm=false so modules are warmed at startup.","Set HTTP server write timeouts above the obfuscation cost.","Watch pre-warmer logs; a stopped pre-warmer causes sync generation on the request path.","Size hosts for the ~5s obfuscation under peak load."],"tags":["appsec","challenge","dynamic-module","obfuscation"],"backgroundTag":"module-init-failed","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"}