{"record":{"id":"805f43b04820a3f5","repo":"crowdsecurity/crowdsec","slug":"generate-pow-salt-w","errorCode":null,"errorMessage":"generate PoW salt: %w","messagePattern":"generate PoW salt: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/appsec/challenge/challenge.go","lineNumber":567,"sourceCode":"\t\tchallengeCode = c.getChallengeCode()\n\t\tif challengeCode == \"\" {\n\t\t\treturn \"\", errors.New(\"challenge JS cache is empty\")\n\t\t}\n\t}\n\n\t// Issuance is stateless (only submission is stateful — see the single-use\n\t// burn in ValidateChallengeResponse). `r` seeds the per-challenge secret\n\t// `s = HMAC(K_epoch, r)` the client derives from the obfuscated dynamic\n\t// module, so `s` never appears in plain HTML; the PoW MAC binds the salt to\n\t// `r`+ts so a client can't pick a favorable salt.\n\tts := fmt.Sprintf(\"%d\", time.Now().UnixNano())\n\tr, err := generateChallengeNonce()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tpowSalt, err := generatePowPrefix()\n\tif err != nil {\n\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)","sourceCodeStart":549,"sourceCodeEnd":585,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/appsec/challenge/challenge.go#L549-L585","documentation":"GetChallengePage generates a random PoW salt prefix via generatePowPrefix (crypto/rand backed) for each challenge. Failure of the underlying random source is wrapped as 'generate PoW salt'. Without a salt, the PoW MAC cannot be computed and no challenge can be issued.","triggerScenarios":"Calling GetChallengePage when generatePowPrefix() returns an error — i.e. the crypto/rand reader fails or the derived prefix is invalid (empty source, broken entropy pool).","commonSituations":"Broken or exhausted system entropy (unusual on modern Linux); tampered/custom builds where the RNG was stubbed; running in exotic sandboxes lacking /dev/urandom access.","solutions":["Verify /dev/urandom is readable in the container/sandbox (mount it if missing).","Check the wrapped error — it reflects the crypto/rand failure; fix the OS-level entropy source.","Restart the process; transient RNG reader failures usually clear on retry.","If on an exotic platform, confirm the Go runtime supports its RNG backend there."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"page, err := rt.GetChallengePage(ctx, opts)\nif err != nil {\n    if strings.Contains(err.Error(), \"generate PoW salt\") {\n        logger.WithError(err).Error(\"crypto/rand unavailable; check OS entropy source\")\n    }\n}","preventionTips":["Ensure /dev/urandom is available and readable in containers.","Avoid sandboxes that block the go runtime RNG sources.","Restart on transient RNG failures; they rarely persist.","Monitor host entropy health on minimal/embedded images."],"tags":["appsec","challenge","crypto","random"],"backgroundTag":"internal-invariant-violation","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}