crowdsecurity/crowdsec · error
ErrCookieExpired
ErrCookieExpired
Error message
%w: not_after=%d now=%d
What it means
openCookieV0Bytes rejects the cookie because its not_after timestamp is <= the current time, i.e. the challenge cookie has reached its expiration. It wraps ErrCookieExpired, which callers match with errors.Is to distinguish expiry from other malformed-cookie failures.
Source
Thrown at pkg/appsec/challenge/crypto.go:259
if len(plaintext) < cookiePlaintextFixedHeaderLen {
return nil, fmt.Errorf("%w: plaintext shorter than fixed header", ErrCookieMalformed)
}
notAfter := int64(binary.BigEndian.Uint64(plaintext[:8]))
flags := plaintext[8]
reasonLen := int(binary.BigEndian.Uint16(plaintext[9:11]))
if reasonLen > MaxAllowlistReasonLen {
return nil, fmt.Errorf("%w: reason_len=%d", ErrCookieMalformed, reasonLen)
}
if len(plaintext) < cookiePlaintextFixedHeaderLen+reasonLen {
return nil, fmt.Errorf("%w: plaintext shorter than declared reason_len", ErrCookieMalformed)
}
if notAfter <= now.Unix() {
return nil, fmt.Errorf("%w: not_after=%d now=%d", ErrCookieExpired, notAfter, now.Unix())
}
reasonStart := cookiePlaintextFixedHeaderLen
reasonEnd := reasonStart + reasonLen
reason := string(plaintext[reasonStart:reasonEnd])
envelope := &pb.ChallengeCookie{}
if err := proto.Unmarshal(plaintext[reasonEnd:], envelope); err != nil {
return nil, fmt.Errorf("%w: %w", ErrCookiePayload, err)
}
return &CookieEnvelope{
Envelope: envelope,
Allowlisted: flags&cookieFlagAllowlisted != 0,
AllowlistReason: reason,
NotAfter: notAfter,
}, nil
}View on GitHub (pinned to 909b515798)
Solutions
- Have the client obtain a fresh challenge cookie and retry
- Synchronize clocks (NTP) across all instances sharing the master_secret
- Increase the cookie validity window at issuance if legitimate lifetimes are being cut short
- Treat ErrCookieExpired as a normal flow: clear the expired cookie client-side and restart the challenge
Example fix
// before: generic handling
if err != nil { return err }
// after
if errors.Is(err, challenge.ErrCookieExpired) {
return issueNewChallenge() // fresh cookie, not an error for the user
} Defensive patterns
Strategy: try-catch
Validate before calling
if errors.Is(err, challenge.ErrCookieExpired) { /* refresh */ } Type guard
func isCookieExpired(err error) bool { return errors.Is(err, challenge.ErrCookieExpired) } Try / catch
env, err := rt.OpenCookie(raw, now)
if errors.Is(err, challenge.ErrCookieExpired) {
clearClientCookie(w)
return issueNewChallenge(w, r)
} else if err != nil {
return err
} Prevention
- Refresh cookies client-side before their TTL elapses
- Run NTP on all instances sharing the master secret
- Set cookie validity windows comfortably above expected session lengths
- In tests, inject time instead of replaying recorded cookies
When it happens
Trigger: openCookie is called after the cookie's not_after timestamp has passed — the cookie was issued more than its validity window ago, or the verifier's clock is ahead of the issuer's.
Common situations: A browser or API client caches the challenge cookie past its TTL and replays it; clock skew between distributed CrowdSec instances behind a load balancer; long-lived sessions resuming after keyring rotation; tests replaying a recorded cookie.
Related errors
- cookie expired
- unknown cookie version
- allowlist reason exceeds maximum length
- cookie exceeds maximum size
- cookie %s not found
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/b37422abb63e6a1b.
Report an issue: GitHub.