crowdsecurity/crowdsec · error

build dynamic key module

Error message

build dynamic key module: %w

What it means

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.

Solutions

  1. Ensure skipPreWarm is false so the module is pre-warmed at startup.
  2. Increase the request/HTTP server timeout to tolerate a synchronous ~5s obfuscation.
  3. Check logs for pre-warmer failures — fix the underlying obfuscation/generation error.
  4. Restart the runtime if the pre-warmer goroutine was stopped without a reload.
Defensive patterns

Strategy: fallback

Try / catch

page, err := rt.GetChallengePage(ctx, opts)
if err != nil {
    if strings.Contains(err.Error(), "build dynamic key module") {
        logger.WithError(err).Warn("dynamic module unavailable; serving static challenge only")
    }
}

Prevention

When it happens

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

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

Related errors


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

Appendix: source

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

		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)
	}

	var renderedPage strings.Builder

	if err := c.htmlTpl.Execute(&renderedPage, map[string]interface{}{
		"JSChallenge":   challengeCode,
		"DynamicModule": dynamicModule,
		"FPScannerPath": ChallengeFPScannerPath,
		"PowDifficulty": difficulty,
		"PowPrefix":     powSalt,
		"PowMAC":        powMAC,
		"Timestamp":     ts,
		"R":             r,
	}); err != nil {
		return "", fmt.Errorf("render challenge page: %w", err)
	}
	return renderedPage.String(), nil
}

View on GitHub (pinned to 909b515798)