crowdsecurity/crowdsec · error

warm dynamic key module: %w

Error message

warm dynamic key module: %w

What it means

After seeding the initial bundle, NewChallengeRuntime pre-warms the current epoch's dynamic key module so the first GetChallengePage does not pay the ~5s obfuscation cost on the request path. If currentDynamicModule fails during this synchronous pre-warm, the constructor fails with this wrapped error.

Source

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

	// Load the build-time-obfuscated challenge code from the baked-in bundle so
	// we can serve immediately.
	if err := challengeRuntime.seedCacheFromInitialBundle(); err != nil {
		// Initial bundle missing/corrupt (e.g. `go generate` not run): fall back
		// to obfuscating the challenge code synchronously.
		logger.Warnf("failed to load baked-in initial challenge bundle (%v); falling back to synchronous generation", err)
		if err := challengeRuntime.generateAndCacheChallengeJS(ctx); err != nil {
			return nil, fmt.Errorf("failed to generate initial challenge bundle: %w", err)
		}
	}

	// Pre-warm the current epoch's dynamic module so the first GetChallengePage
	// doesn't pay the ~5s obfuscation cost on the request path. The background
	// pre-warmer then re-obfuscates on every rotation; it runs under a context
	// owned by Close() rather than the constructor ctx, so a reload (which
	// reuses the process ctx) can stop it — see Close().
	if !resolvedOpts.skipPreWarm {
		if _, err := challengeRuntime.currentDynamicModule(ctx); err != nil {
			return nil, fmt.Errorf("warm dynamic key module: %w", err)
		}

		runCtx, cancel := context.WithCancel(ctx)
		challengeRuntime.preWarmCancel = cancel
		go challengeRuntime.dynamicModulePreWarmer(runCtx)
	}

	logger.WithFields(log.Fields{
		"rotation_interval": rotationInterval,
		"cookie_ttl":        cookieTTL,
		"max_cookie_len":    maxCookieLen,
		"pow_difficulty":    defaultPowDifficulty,
		"crypto_pool_size":  cryptoPoolSize,
	}).Info("WAF challenge runtime initialized")

	return challengeRuntime, nil
}

View on GitHub (pinned to 909b515798)

Solutions

  1. Increase the deadline on the context passed to NewChallengeRuntime to allow the ~5s obfuscation to complete.
  2. Check the wrapped error for the true cause (obfuscation failure, ctx cancellation) and address it.
  3. Set skipPreWarm in options only if you accept paying the cost on the first request — not a real fix, but unblocks startup.
  4. Verify system resources (CPU/memory) on the host running CrowdSec AppSec.

Example fix

// before
ctx, cancel := context.WithTimeout(parentCtx, time.Second)
// after: allow for the ~5s obfuscation cost
ctx, cancel := context.WithTimeout(parentCtx, 30*time.Second)
Defensive patterns

Strategy: fallback

Try / catch

rt, err := NewChallengeRuntime(ctx, opts)
if err != nil && strings.Contains(err.Error(), "warm dynamic key module") {
    logger.WithError(err).Error("pre-warm failed; first requests will be slow or fail")
}

Prevention

When it happens

Trigger: Calling NewChallengeRuntime with skipPreWarm=false when the synchronous call to currentDynamicModule(ctx) errors — typically because the underlying obfuscation/generation step failed or ctx was cancelled during pre-warm.

Common situations: Startup contexts with too-short deadlines; hosts too slow/memory-constrained to run the JS obfuscator within the deadline; an already-cancelled context from a reload path.

Related errors


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