crowdsecurity/crowdsec · info

cookie expired

Error message

cookie expired

What it means

ErrCookieExpired is a sentinel error in the challenge cookie crypto layer indicating that a challenge cookie's not_after timestamp has passed. openCookieV0Bytes compares the cookie's embedded not_after value against the current wall clock and wraps this sentinel with the concrete timestamps. appsec.go maps it to the "epoch" remediation so the client gets a fresh challenge instead of an opaque 500.

Source

Thrown at pkg/appsec/challenge/crypto.go:31

	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/binary"
	"errors"
	"fmt"
	"time"

	"golang.org/x/crypto/hkdf"

	"github.com/crowdsecurity/crowdsec/pkg/appsec/challenge/pb"
	"google.golang.org/protobuf/proto"
)

var (
	ErrCookieMalformed     = errors.New("malformed cookie")
	ErrCookieSignature     = errors.New("invalid cookie signature")
	ErrCookiePayload       = errors.New("invalid cookie payload")
	ErrCookieExpired       = errors.New("cookie expired")
	ErrCookieVersion       = errors.New("unknown cookie version")
	ErrAllowlistReasonSize = errors.New("allowlist reason exceeds maximum length")
	ErrCookieTooLarge      = errors.New("cookie exceeds maximum size")
)

const hkdfInfo = "crowdsec-challenge-cookie"

// MaxAllowlistReasonLen caps the reason string operators pass to
// GrantChallengeCookie. The reason travels inside every Set-Cookie + Cookie
// header round-trip until the cookie expires; bounding it keeps the cookie
// well under the 4 KB browser limit even with the AES-GCM tag + base64
// expansion.
const MaxAllowlistReasonLen = 256

// MaxCookieLen is the DEFAULT per-cookie size (RFC 6265 §6.1: 4096 bytes).
// Can be configured via Config.MaxCookieSize and we reject anything bigger.
const MaxCookieLen = 4096

View on GitHub (pinned to 909b515798)

Solutions

  1. Let the client re-solve the challenge: the "epoch" remediation already issues a fresh cookie, so treat this as expected flow, not a bug.
  2. Check for clock skew (NTP) if valid users are systematically rejected.
  3. If widespread, verify the cookie TTL configuration is not set to an unreasonably small value.
  4. Ensure clients are not caching/persisting cookies beyond their TTL (e.g. proxies replaying Set-Cookie).

Example fix

// before: treating any cookie error as fatal
if err := openCookie(raw, key, aad); err != nil {
    return fmt.Errorf("cookie rejected: %w", err)
}
// after: branch on expiry and reissue
if errors.Is(err, challenge.ErrCookieExpired) {
    return issueFreshChallenge() // "epoch" remediation
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: no pre-check of expiry is possible without opening the cookie;
// keep cookie TTL comfortably above expected session length
const cookieTTL = 10 * time.Minute

Try / catch

if err := openCookie(raw, key, aad); err != nil {
    switch {
    case errors.Is(err, challenge.ErrCookieExpired):
        // expected: reissue via "epoch" remediation
        return issueFreshChallenge()
    default:
        return err
    }
}

Prevention

When it happens

Trigger: A client presents a Cookie whose embedded not_after <= time.Now().Unix() during openCookieV0Bytes; also asserted in tests TestCookieV0_ExpiredRejected and TestCookieV0_ExpiryEnforcedAgainstWallClock.

Common situations: A user keeps a browser tab open past the cookie TTL and retries; a clock skew between issuing and validating nodes; replaying an old captured cookie; stale cookies after the master key rotation left them unrotated.

Related errors


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