{"record":{"id":"45fd57b5e19450bc","repo":"crowdsecurity/crowdsec","slug":"generate-pow-prefix-w","errorCode":null,"errorMessage":"generate PoW prefix: %w","messagePattern":"generate PoW prefix: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"pkg/appsec/challenge/ticket.go","lineNumber":105,"sourceCode":"// block, which is what makes it fast enough to justify the difficulty levels\n// above. That budget is 55 bytes, and a base36 nonce can reach 11 chars, so the\n// hex salt has to fit in the rest. The solver refuses to run on a salt that\n// doesn't, this is the server's half of\n// the contract. TestPowSaltFitsClientFastPath guards it.\nconst (\n\tpowSaltBytes     = 16\n\tpowSaltMaxHexLen = 44\n)\n\n// generatePowPrefix returns a freshly-generated random PoW salt rendered as a\n// hex string. Errors from crypto/rand.Read indicate a broken kernel entropy\n// pool, which is recoverable at the request layer (we can reject the current\n// challenge and let the client retry) — returning the error rather than\n// panicking keeps a single failing request from taking down the whole WAF.\nfunc generatePowPrefix() (string, error) {\n\tbuf := make([]byte, powSaltBytes)\n\tif _, err := crand.Read(buf); err != nil {\n\t\treturn \"\", fmt.Errorf(\"generate PoW prefix: %w\", err)\n\t}\n\n\treturn hex.EncodeToString(buf), nil\n}\n\n// computePowMAC authenticates a PoW salt as server-generated and bound to a\n// specific challenge (`r`) + timestamp + difficulty, signed with the per-epoch\n// key.\nfunc (c *ChallengeRuntime) computePowMAC(salt, r, ts string, difficulty int) string {\n\tepoch := c.epochForTimestamp(ts)\n\tsignKey, ok := c.keys.SignKey(epoch)\n\tif !ok {\n\t\t_, signKey = c.keys.Current()\n\t}\n\n\th := hmac.New(sha256.New, signKey)\n\th.Write([]byte(salt))\n\th.Write([]byte(r))","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/appsec/challenge/ticket.go#L87-L123","documentation":"generatePowPrefix reads powSaltBytes of crypto/rand entropy to build the hex-encoded salt/prefix the client must hash for the proof-of-work challenge. The error wraps a crypto/rand read failure. Like the nonce generator, it returns an error rather than panicking so one failing request does not take down the WAF; the caller can reject the current challenge and let the client retry.","triggerScenarios":"Calling generatePowPrefix (via GetChallengePage or mustGeneratePowPrefix) when crand.Read fails to deliver bytes from the OS entropy source.","commonSituations":"Same as nonce generation: seccomp profiles blocking getrandom in hardened containers, broken /dev/urandom, unusual platforms. Practically never seen on healthy Linux hosts.","solutions":["Check the OS entropy source: `head -c 16 /dev/urandom | xxd`","Review container syscall filters (seccomp) to ensure getrandom(2) is allowed","Restart the machine or container to recover the random subsystem","If persistent, inspect dmesg for entropy/cr ng related kernel errors"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"null","typeGuard":null,"tryCatchPattern":"prefix, err := generatePowPrefix()\nif err != nil {\n    log.Errorf(\"pow prefix unavailable: %v\", err)\n    // reject this challenge, client can retry\n    return err\n}","preventionTips":["Same entropy hygiene as nonce generation: allow getrandom in sandbox profiles","Alert on entropy-source errors before they block challenge issuance"],"tags":["crypto","entropy","appsec","pow"],"backgroundTag":"entropy-source-unavailable","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"}