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

  1. Have the client request a fresh challenge page and re-solve it, so a new valid ticket is minted
  2. If behind a load balancer, ensure all crowdsec AppSec instances share ticket/secret state or route the same client consistently
  3. Check server clock and ticket TTL configuration if tickets expire too quickly
  4. 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

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


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