crowdsecurity/crowdsec · warning

invalid challenge cookie: %w

Error message

invalid challenge cookie: %w

What it means

ValidCookie unseals and validates the client's challenge cookie (AES-GCM envelope integrity, expiration, user-agent pinning). This wrapper means openCookie rejected the cookie for any reason: malformed base64, bad GCM signature, expired not_after, unknown version, oversized value, or a user-agent mismatch breaking the AEAD authentication.

Source

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

	Fingerprint     FingerprintData
	PowDifficulty   int
	Allowlisted     bool
	AllowlistReason string
}

// ValidCookie unseals and validates a challenge cookie: envelope (version,
// AES-GCM tag), not_after expiry, and UA-pinning (a stolen cookie is useless to
// a different client). On any failure (tampered/expired/UA-mismatch/unknown
// version) it returns an error and the caller should treat the request as
// cookieless.
func (c *ChallengeRuntime) ValidCookie(ck *http.Cookie, userAgent string) (*CookieData, error) {
	if ck == nil {
		return nil, errors.New("nil cookie")
	}

	envelope, err := openCookie(ck.Value, c.keys.MasterCookieKey(), []byte(userAgent), c.maxCookieLen)
	if err != nil {
		return nil, fmt.Errorf("invalid challenge cookie: %w", err)
	}

	return &CookieData{
		Fingerprint:     fingerprintDataFromProto(envelope.Envelope.GetFingerprint()),
		PowDifficulty:   int(envelope.Envelope.GetPowDifficulty()),
		Allowlisted:     envelope.Allowlisted,
		AllowlistReason: envelope.AllowlistReason,
	}, nil
}

View on GitHub (pinned to 909b515798)

Solutions

  1. Treat this as 'no valid cookie': the caller should serve the challenge again rather than error out to the client.
  2. Ensure all instances in a distributed deployment share the identical master_secret config value.
  3. Check whether the User-Agent differs between cookie mint and validation; the UA is AAD-pinned to the cookie.
  4. Confirm the cookie is within cookie_ttl; expired cookies must be re-minted by solving the challenge.
  5. If errors appear fleet-wide after a restart, configure a persistent master_secret so cookies survive restarts.

Example fix

ck, err := r.Cookie(challenge.ChallengeCookieName)
if err != nil {
    return serveChallenge(w, req)
}
cd, err := rt.ValidCookie(ck, req.UserAgent())
if err != nil {
    log.Debug().Err(err).Msg("invalid challenge cookie, re-challenging")
    return serveChallenge(w, req)
}
Defensive patterns

Strategy: fallback

Validate before calling

if ck == nil || ck.Value == "" || len(ck.Value) > challenge.MaxCookieLen {
    return serveChallenge(w, req)
}

Type guard

func validChallengeCookie(ck *http.Cookie) bool { return ck != nil && ck.Value != "" && len(ck.Value) <= challenge.MaxCookieLen }

Try / catch

cd, err := rt.ValidCookie(ck, req.UserAgent())
if err != nil {
    log.Debug().Err(err).Msg("invalid challenge cookie; serving challenge")
    return serveChallenge(w, req)
}

Prevention

When it happens

Trigger: Calling ChallengeRuntime.ValidCookie(cookie, userAgent) with a cookie that is expired (not_after <= now), tampered with, sealed under a different master_secret, sent from a different User-Agent than when minted, corrupt, larger than maxCookieLen, or using an unknown version byte.

Common situations: Client browsers holding a cookie older than cookie_ttl (default 12h); load-balanced fleets where instances have different master_secret values; cookie replayed from a different browser/UA than the one that solved the challenge; shared master_secret rotated on only some nodes; stale cookies surviving an application restart when master_secret is ephemeral.

Related errors


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