crowdsecurity/crowdsec · error
SendChallenge can only be called from an in-band hook (on_ch
Error message
SendChallenge can only be called from an in-band hook (on_challenge or post_eval)
What it means
SendChallenge drives the challenge/captcha flow for a request, but that flow only exists in the in-band phase of request processing (during on_challenge or post_eval hooks, which share the same expression environment). Calling it when the request state's CurrentPhase is not PhaseInBand is refused, because out-of-band processing cannot mint challenge pages or cookies.
Source
Thrown at pkg/appsec/appsec.go:1634
for _, sig := range report.Signals {
metrics.AppsecFingerprintMismatch.With(prometheus.Labels{
"reason": sig.Reason,
"severity": sig.Severity,
"appsec_engine": request.AppsecEngine,
}).Inc()
}
}
func (w *AppsecRuntimeConfig) SendChallenge(ctx context.Context, state *AppsecRequestState, request *ParsedRequest) error {
if w.ChallengeRuntime == nil {
return errors.New("challenge runtime not initialized")
}
// SendChallenge can only be called from inband.post_eval and inband.on_challenge.
// as it's the same expr-env, we need to detect here.
if state.CurrentPhase != PhaseInBand {
return errors.New("SendChallenge can only be called from an in-band hook (on_challenge or post_eval)")
}
// GrantChallengeCookie earlier in the same request already minted an
// allowlist cookie; refuse to overwrite it with a challenge page.
if state.ChallengeBypassed {
w.Logger.Debugf("SendChallenge no-op: allowlist cookie already granted this request")
return nil
}
// A hook flagged this request as exempt (verified bot, well-known path, ...).
if state.ChallengeExempt {
w.Logger.Debugf("SendChallenge no-op: request exempt from challenge")
return nil
}
target := w.ChallengeRuntime.Difficulty()
if state.ChallengeDifficulty != nil {
target = *state.ChallengeDifficultyView on GitHub (pinned to 909b515798)
Solutions
- Move the SendChallenge call into an in-band hook: inband.on_challenge or inband.post_eval
- If the intent is to block bot traffic in out-of-band processing, use a ban/drop remediation instead of SendChallenge
- Ensure the rule collection containing the call is only loaded under the in-band WAF component
Example fix
// before (out-of-band rule)
outofband:
post_eval:
- SendChallenge()
// after
inband:
post_eval:
- SendChallenge() Defensive patterns
Strategy: validation
Validate before calling
if state.CurrentPhase != PhaseInBand {
return fmt.Errorf("SendChallenge requires in-band phase; current phase: %s", state.CurrentPhase)
} Prevention
- Only reference SendChallenge in inband.on_challenge or inband.post_eval hooks
- Do not share rule sets containing SendChallenge between in-band and out-of-band components
When it happens
Trigger: Invoking the SendChallenge expression function from an out-of-band hook (e.g. outofband.on_load or outofband pre/post-eval), or from any phase other than inband.on_challenge / inband.post_eval.
Common situations: A user copies a SendChallenge call into an out-of-band rule collection; a shared rule set is loaded both in-band and out-of-band and the hook runs in the out-of-band path.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- appsec datasource requires a hub. this is a bug, please repo
- appsec datasource requires a lapi client configuration. this
- missing lapi client credentials
- no appsec_config provided
- missing TLS key file
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/829788cd3f4dd0e7.
Report an issue: GitHub.