crowdsecurity/crowdsec · warning
ErrChallengeTicket
ErrChallengeTicket
Error message
invalid ticket in challenge response
What it means
ErrChallengeTicket is a sentinel reason returned by ValidateChallengeResponse when the ticket embedded in the client's challenge answer is invalid — absent, expired, signed incorrectly, or not matching a challenge the server issued. The AppSec layer maps it to the reason code "ticket" when reporting the failed challenge.
Source
Thrown at pkg/appsec/challenge/challenge.go:60
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"golang.org/x/sync/singleflight"
"golang.org/x/sys/cpu"
)
// Internal URL paths the challenge runtime intercepts. Bouncers MUST forward
// these to the WAF unmodified; they are served by the appsec dispatcher
// (pkg/appsec/appsec.go) rather than by the protected origin.
const (
ChallengeJSPath = "/crowdsec-internal/challenge/challenge.js"
ChallengeSubmitPath = "/crowdsec-internal/challenge/submit"
ChallengePowWorkerPath = "/crowdsec-internal/challenge/pow-worker.js"
ChallengeFPScannerPath = "/crowdsec-internal/challenge/fpscanner.js"
)
// Sentinel errors (reasons) returned by ValidateChallengeResponse.
var (
ErrChallengeFields = errors.New("missing required fields in challenge response")
ErrChallengeTicket = errors.New("invalid ticket in challenge response")
ErrChallengeDifficulty = errors.New("challenge difficulty is impossible")
ErrChallengePoW = errors.New("invalid proof-of-work in challenge response")
ErrChallengeHMAC = errors.New("invalid HMAC in challenge response")
ErrChallengePayload = errors.New("invalid challenge response payload")
)
// ChallengeCookieName is the name of the sealed cookie carrying the
// successfully-validated fingerprint between requests.
const ChallengeCookieName = "__crowdsec_challenge"
// cryptoObfuscationPoolDefaultSize is how many obfuscations of the per-epoch
// key module to keep per live epoch. Each variant embeds the same key
// differently (per-visitor byte variance); default 1 keeps prior behavior.
const cryptoObfuscationPoolDefaultSize = 1
// defaultCookieTTL is the default challenge-cookie validity. Decoupled from the
// keyring window (enforced by not_after in the envelope), so cookies can
// outlive the per-epoch signing window without widening forgery exposure.View on GitHub (pinned to 909b515798)
Solutions
- Have the client request a fresh challenge page and re-solve it, so a new valid ticket is minted
- If behind a load balancer, ensure all crowdsec AppSec instances share ticket/secret state or route the same client consistently
- Check server clock and ticket TTL configuration if tickets expire too quickly
- Verify no intermediary rewrites or truncates the ticket field in the submitted response
Defensive patterns
Strategy: try-catch
Try / catch
if errors.Is(err, challenge.ErrChallengeTicket) {
// return reason "ticket" and re-issue a new challenge page to the client
w.Logger.Debugf("challenge ticket invalid: %v", err)
} Prevention
- Share challenge signing secrets/state across load-balanced crowdsec nodes
- Set ticket TTLs long enough for users to solve the challenge
- Serve a fresh challenge automatically on ticket errors
When it happens
Trigger: A client submits a challenge response whose ticket fails server-side validation, e.g. the ticket was issued before a crowdsec restart, the server-side signing state changed, or the ticket was tampered with.
Common situations: Load-balanced setups where challenge state is not shared between crowdsec instances; a long-running challenge solved after ticket expiry; clients replaying an old challenge response; clock skew affecting ticket validity.
Related errors
- ErrChallengeFields
- unable to get challenge page: %w
- unable to seal allowlist cookie: %w
- failed to generate initial challenge bundle: %w
- warm dynamic key module: %w
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/a876ce8b5a67db64.
Report an issue: GitHub.