crowdsecurity/crowdsec · error

generate PoW salt: %w

Error message

generate PoW salt: %w

What it means

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.

Source

Thrown at pkg/appsec/challenge/challenge.go:567

		challengeCode = c.getChallengeCode()
		if challengeCode == "" {
			return "", errors.New("challenge JS cache is empty")
		}
	}

	// Issuance is stateless (only submission is stateful — see the single-use
	// burn in ValidateChallengeResponse). `r` seeds the per-challenge secret
	// `s = HMAC(K_epoch, r)` the client derives from the obfuscated dynamic
	// module, so `s` never appears in plain HTML; the PoW MAC binds the salt to
	// `r`+ts so a client can't pick a favorable salt.
	ts := fmt.Sprintf("%d", time.Now().UnixNano())
	r, err := generateChallengeNonce()
	if err != nil {
		return "", err
	}
	powSalt, err := generatePowPrefix()
	if err != nil {
		return "", fmt.Errorf("generate PoW salt: %w", err)
	}
	powMAC := c.computePowMAC(powSalt, r, ts, difficulty)

	if c.log().Logger.IsLevelEnabled(log.DebugLevel) {
		issEpoch, issKey := c.keys.Current()
		c.log().WithFields(log.Fields{
			"r":          r,
			"epoch":      issEpoch,
			"k_epoch":    fmt.Sprintf("%x", issKey),
			"difficulty": difficulty,
		}).Debug("issued challenge")
	}

	// The challenge code only carries the hook registration; the dynamic module
	// carries the per-epoch K, so K never appears in plain HTML.
	dynamicModule, err := c.currentDynamicModule(ctx)
	if err != nil {
		return "", fmt.Errorf("build dynamic key module: %w", err)

View on GitHub (pinned to 909b515798)

Solutions

  1. Verify /dev/urandom is readable in the container/sandbox (mount it if missing).
  2. Check the wrapped error — it reflects the crypto/rand failure; fix the OS-level entropy source.
  3. Restart the process; transient RNG reader failures usually clear on retry.
  4. If on an exotic platform, confirm the Go runtime supports its RNG backend there.
Defensive patterns

Strategy: retry

Try / catch

page, err := rt.GetChallengePage(ctx, opts)
if err != nil {
    if strings.Contains(err.Error(), "generate PoW salt") {
        logger.WithError(err).Error("crypto/rand unavailable; check OS entropy source")
    }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/805f43b04820a3f5. Report an issue: GitHub.