{"record":{"id":"effb3d0e7caed339","repo":"crowdsecurity/crowdsec","slug":"cookie-expired","errorCode":null,"errorMessage":"cookie expired","messagePattern":"cookie expired","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"pkg/appsec/challenge/crypto.go","lineNumber":31,"sourceCode":"\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"golang.org/x/crypto/hkdf\"\n\n\t\"github.com/crowdsecurity/crowdsec/pkg/appsec/challenge/pb\"\n\t\"google.golang.org/protobuf/proto\"\n)\n\nvar (\n\tErrCookieMalformed     = errors.New(\"malformed cookie\")\n\tErrCookieSignature     = errors.New(\"invalid cookie signature\")\n\tErrCookiePayload       = errors.New(\"invalid cookie payload\")\n\tErrCookieExpired       = errors.New(\"cookie expired\")\n\tErrCookieVersion       = errors.New(\"unknown cookie version\")\n\tErrAllowlistReasonSize = errors.New(\"allowlist reason exceeds maximum length\")\n\tErrCookieTooLarge      = errors.New(\"cookie exceeds maximum size\")\n)\n\nconst hkdfInfo = \"crowdsec-challenge-cookie\"\n\n// MaxAllowlistReasonLen caps the reason string operators pass to\n// GrantChallengeCookie. The reason travels inside every Set-Cookie + Cookie\n// header round-trip until the cookie expires; bounding it keeps the cookie\n// well under the 4 KB browser limit even with the AES-GCM tag + base64\n// expansion.\nconst MaxAllowlistReasonLen = 256\n\n// MaxCookieLen is the DEFAULT per-cookie size (RFC 6265 §6.1: 4096 bytes).\n// Can be configured via Config.MaxCookieSize and we reject anything bigger.\nconst MaxCookieLen = 4096\n","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/appsec/challenge/crypto.go#L13-L49","documentation":"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.","triggerScenarios":"A client presents a Cookie whose embedded not_after <= time.Now().Unix() during openCookieV0Bytes; also asserted in tests TestCookieV0_ExpiredRejected and TestCookieV0_ExpiryEnforcedAgainstWallClock.","commonSituations":"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.","solutions":["Let the client re-solve the challenge: the \"epoch\" remediation already issues a fresh cookie, so treat this as expected flow, not a bug.","Check for clock skew (NTP) if valid users are systematically rejected.","If widespread, verify the cookie TTL configuration is not set to an unreasonably small value.","Ensure clients are not caching/persisting cookies beyond their TTL (e.g. proxies replaying Set-Cookie)."],"exampleFix":"// before: treating any cookie error as fatal\nif err := openCookie(raw, key, aad); err != nil {\n    return fmt.Errorf(\"cookie rejected: %w\", err)\n}\n// after: branch on expiry and reissue\nif errors.Is(err, challenge.ErrCookieExpired) {\n    return issueFreshChallenge() // \"epoch\" remediation\n}","handlingStrategy":"try-catch","validationCode":"// Go: no pre-check of expiry is possible without opening the cookie;\n// keep cookie TTL comfortably above expected session length\nconst cookieTTL = 10 * time.Minute","typeGuard":null,"tryCatchPattern":"if err := openCookie(raw, key, aad); err != nil {\n    switch {\n    case errors.Is(err, challenge.ErrCookieExpired):\n        // expected: reissue via \"epoch\" remediation\n        return issueFreshChallenge()\n    default:\n        return err\n    }\n}","preventionTips":["Set cookie TTL generously relative to user session length","Sync clocks (NTP) across all crowdsec nodes","Treat ErrCookieExpired as normal flow and reissue, never as a 5xx","Monitor expiry rates to catch TTL misconfiguration"],"tags":["appsec","cookie","crypto","expiry"],"backgroundTag":"jwt-token-expired","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}