crowdsecurity/crowdsec · critical
generate challenge nonce: %w
Error message
generate challenge nonce: %w
What it means
generateChallengeNonce reads 16 bytes of crypto/rand entropy and hex-encodes them as the per-challenge nonce `r` used to derive the challenge signing secret. The error wraps a crypto/rand read failure, meaning the OS entropy source could not supply bytes. It is thrown as an error (not a panic) so only the current request fails instead of the whole WAF.
Source
Thrown at pkg/appsec/challenge/ticket.go:60
)
// ticketAgeBackstop is a loose ceiling on accepted submission age in
// verifyChallenge. The actual freshness gate is the keyring live
// window (rotationInterval × maxLiveEpochs); this is a separate ceiling
// that protects against operators configuring an unusually wide live
// window. Loose enough not to interfere with real submissions on slow
// clients (high-difficulty PoW) but tight enough to bound replay
// surface in pathological configurations.
const ticketAgeBackstop = 20 * time.Minute
// generateChallengeNonce returns a fresh 16-byte random per-challenge nonce
// (`r`) as hex. `r` keys single-use bookkeeping (spent_set.go) and seeds the
// per-challenge secret `s = HMAC(K_epoch, r)`. Error (not panic) on entropy
// failure so only the current request fails.
func generateChallengeNonce() (string, error) {
buf := make([]byte, 16)
if _, err := crand.Read(buf); err != nil {
return "", fmt.Errorf("generate challenge nonce: %w", err)
}
return hex.EncodeToString(buf), nil
}
// deriveChallengeSecret computes the per-challenge signing secret
// `s = HMAC(K_epoch, r)` (hex). `s` is never transmitted; client and server
// derive it independently from the same per-epoch key.
func deriveChallengeSecret(signKey []byte, r string) string {
return hmacSHA256Hex(signKey, []byte(r))
}
// epochForTimestamp converts a nanosecond UnixNano string (the format used in
// challenge.go's ts) into the keyring's epoch identifier. Uses the same
// rotation interval as the keyring so two instances always agree.
func (c *ChallengeRuntime) epochForTimestamp(ts string) int64 {
tsVal, err := strconv.ParseInt(ts, 10, 64)
if err != nil || tsVal <= 0 {View on GitHub (pinned to 909b515798)
Solutions
- Verify the kernel random source works: run `head -c 16 /dev/urandom | xxd` on the host running crowdsec
- Check container seccomp/apparmor profiles allow the getrandom(2) syscall
- Restart the host/container to restore the entropy subsystem
- Update to a newer Go/runtime version if the failure is a known getrandom bug on your platform
Example fix
// before
nonce, err := generateChallengeNonce()
if err != nil { panic(err) }
// after
nonce, err := generateChallengeNonce()
if err != nil {
log.Errorf("challenge nonce unavailable: %v", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
} Defensive patterns
Strategy: try-catch
Validate before calling
null
Try / catch
nonce, err := generateChallengeNonce()
if err != nil {
log.Errorf("challenge nonce unavailable: %v", err)
// fail only this request
return err
} Prevention
- Keep the default getrandom(2) path available in containers (don't block it with seccomp)
- Monitor for recurring crypto/rand failures in logs
- Pin tested Go versions for your platform
When it happens
Trigger: Calling generateChallengeNonce (directly in tests, or via GetChallengePage/freshChallenge) when crand.Read fails — typically when the OS random source (/dev/urandom, getrandom syscall) is unavailable or returns an error.
Common situations: Hardened containers with restricted syscalls blocking getrandom, exotic sandboxes/seccomp profiles, or heavily degraded kernel entropy conditions. Very rare on normal Linux systems.
Related errors
- failed to generate nonce: %w
- generate PoW prefix: %w
- unable to generate a new random seed for JWT generation
- not enough entropy at random seed generation for JWT generat
- cookie expired
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/a8b5d1a967203f59.
Report an issue: GitHub.