crowdsecurity/crowdsec · error

build dynamic key module: %w

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.

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)

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.