{"record":{"id":"63f4bcf1e3f4f0f9","repo":"ory/hydra","slug":"cookiex-purpose-must-be-non-empty-and-must-not-co","errorCode":null,"errorMessage":"cookiex: purpose must be non-empty and must not contain a pipe character","messagePattern":"cookiex: purpose must be non-empty and must not contain a pipe character","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/cookiex/cookiex.go","lineNumber":85,"sourceCode":"// A Codec is safe for concurrent use.\ntype Codec[T any] struct {\n\tpurpose string\n\tkeys    [][32]byte\n\tmaxAge  time.Duration\n\tlegacy  legacyState\n\tnow     func() time.Time\n}\n\n// New returns a codec for the given purpose. The purpose is bound into the\n// 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 {","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/cookiex/cookiex.go#L67-L103","documentation":"cookiex.New validates the `purpose` string used as domain-separation context for the authenticated encryption. The constructor returns this error when purpose is empty or contains a '|' character, because the pipe is used internally as a separator in the authenticated data and an empty purpose provides no domain separation.","triggerScenarios":"Calling New[T](purpose, secrets, ...) with purpose == \"\" or a purpose containing '|' (oryx/cookiex.go:85), e.g. building a purpose dynamically from a config value that is unset or interpolated.","commonSituations":"Config-driven purpose left blank in YAML/env; concatenating purpose parts with '|' by habit; refactoring that dropped a default purpose constant; copy-paste from code that joined multiple fields with pipes.","solutions":["Pass a non-empty purpose constant, e.g. \"session\" or \"csrf_token\", without '|'.","Validate config at startup so an empty purpose fails at config-load time with a clearer message.","Replace any '|' in dynamic values with '_' before constructing the purpose.","Reuse a shared package-level constant for the purpose so seal/open always agree."],"exampleFix":"// before\ncodec, err := cookiex.New[Session](cfg.SessionPurpose, keys) // cfg.SessionPurpose = \"\"\n// after\npurpose := cfg.SessionPurpose\nif purpose == \"\" { purpose = \"session\" }\npurpose = strings.ReplaceAll(purpose, \"|\", \"_\")\ncodec, err := cookiex.New[Session](purpose, keys)","handlingStrategy":"validation","validationCode":"// validate purpose before constructing the codec\nfunc validatePurpose(p string) error {\n    if p == \"\" || strings.Contains(p, \"|\") {\n        return fmt.Errorf(\"cookie purpose %q must be non-empty and pipe-free\", p)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"codec, err := cookiex.New[Session](purpose, secrets)\nif err != nil {\n    log.Fatalf(\"invalid cookie codec config: %v\", err) // fail fast at boot\n}","preventionTips":["Define purposes as package-level constants","Sanitize any dynamic purpose input (strip '|')","Validate related config at startup, before serving traffic","Add unit tests covering empty and pipe-containing purposes"],"tags":["cookies","go","validation","configuration"],"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"}