{"record":{"id":"d993bf7d3a323d3d","repo":"ory/hydra","slug":"cookiex-max-age-must-not-be-negative","errorCode":null,"errorMessage":"cookiex: max age must not be negative","messagePattern":"cookiex: max age must not be negative","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/cookiex/cookiex.go","lineNumber":95,"sourceCode":"// ciphertext and used as the metric label; it must be a short constant like\n// \"kratos/session\". Because the purpose is embedded in the additional\n// authenticated data, it must be non-empty and must not contain a pipe\n// character. The codec seals with a key derived from the first secret\n// and opens with keys derived from any of them, so secrets rotate by\n// prepending a new one.\nfunc New[T any](purpose string, secrets [][]byte, opts ...Option) (*Codec[T], error) {\n\tif purpose == \"\" || strings.Contains(purpose, \"|\") {\n\t\treturn nil, errors.New(\"cookiex: purpose must be non-empty and must not contain a pipe character\")\n\t}\n\tif len(secrets) == 0 {\n\t\treturn nil, errors.New(\"cookiex: at least one secret is required\")\n\t}\n\tcfg := config{maxAge: defaultMaxAge}\n\tfor _, opt := range opts {\n\t\topt(&cfg)\n\t}\n\tif cfg.maxAge < 0 {\n\t\treturn nil, errors.New(\"cookiex: max age must not be negative\")\n\t}\n\tif cfg.legacyEncode && len(cfg.legacyKeyPairs) == 0 {\n\t\treturn nil, errors.New(\"cookiex: legacy encode requires legacy key pairs\")\n\t}\n\tkeys := make([][32]byte, len(secrets))\n\tfor i, secret := range secrets {\n\t\tkey, err := hkdf.Key(sha256.New, secret, nil, kdfInfo, 32)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"cookiex: cannot derive key\")\n\t\t}\n\t\tkeys[i] = [32]byte(key)\n\t}\n\treturn &Codec[T]{\n\t\tpurpose: purpose,\n\t\tkeys:    keys,\n\t\tmaxAge:  cfg.maxAge,\n\t\tlegacy:  newLegacyState(cfg, cfg.maxAge),\n\t\tnow:     time.Now,","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/cookiex/cookiex.go#L77-L113","documentation":"cookiex.New validates its option-configured settings before constructing the cookie codec. When an option (e.g. WithMaxAge) sets a negative MaxAge value, New refuses to build the codec and returns this error, because cookies with negative lifetimes are invalid/expired-on-issue.","triggerScenarios":"Calling cookiex.New(secrets...) with WithMaxAge(-1) (or any negative duration/seconds value) in the options slice.","commonSituations":"Typo in a time constant (e.g. time.Duration(-1) instead of omitting), computing a duration from a config value that parsed as negative, or passing a 'no expiry' sentinel of -1 that this library does not accept.","solutions":["Use a non-negative MaxAge value in the option (e.g. WithMaxAge(3600) or a positive time.Duration).","To make a session cookie (no explicit expiry), omit the WithMaxAge option and rely on the library default (defaultMaxAge) or the zero/session-cookie path instead of passing -1.","Validate configured durations from env/config before passing them to New.","Wrap New at startup so this fail-fast error surfaces at boot, not at first cookie write."],"exampleFix":"// before\ncc, err := cookiex.New([][]byte{secret}, cookiex.WithMaxAge(-1))\n// after\ncc, err := cookiex.New([][]byte{secret}, cookiex.WithMaxAge(24*3600))","handlingStrategy":"validation","validationCode":"func validateMaxAge(d int) error {\n  if d < 0 {\n    return fmt.Errorf(\"max age must be >= 0, got %d\", d)\n  }\n  return nil\n}\n// call validateMaxAge(cfg.MaxAge) before cookiex.New(...)","typeGuard":null,"tryCatchPattern":"cfg, err := cookiex.New(secrets, opts...)\nif err != nil {\n  return nil, fmt.Errorf(\"cookiex init: %w\", err) // fail fast at boot\n}","preventionTips":["Never pass -1 as MaxAge; omit the option for defaults","Sanity-check durations parsed from env/config before use","Construct cookiex once at startup so errors surface immediately"],"tags":["cookies","configuration","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}