ory/hydra · error
cookiex: purpose must be non-empty and must not contain a pi
Error message
cookiex: purpose must be non-empty and must not contain a pipe character
What it means
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.
Source
Thrown at oryx/cookiex/cookiex.go:85
// A Codec is safe for concurrent use.
type Codec[T any] struct {
purpose string
keys [][32]byte
maxAge time.Duration
legacy legacyState
now func() time.Time
}
// New returns a codec for the given purpose. The purpose is bound into the
// ciphertext and used as the metric label; it must be a short constant like
// "kratos/session". Because the purpose is embedded in the additional
// authenticated data, it must be non-empty and must not contain a pipe
// character. The codec seals with a key derived from the first secret
// and opens with keys derived from any of them, so secrets rotate by
// prepending a new one.
func New[T any](purpose string, secrets [][]byte, opts ...Option) (*Codec[T], error) {
if purpose == "" || strings.Contains(purpose, "|") {
return nil, errors.New("cookiex: purpose must be non-empty and must not contain a pipe character")
}
if len(secrets) == 0 {
return nil, errors.New("cookiex: at least one secret is required")
}
cfg := config{maxAge: defaultMaxAge}
for _, opt := range opts {
opt(&cfg)
}
if cfg.maxAge < 0 {
return nil, errors.New("cookiex: max age must not be negative")
}
if cfg.legacyEncode && len(cfg.legacyKeyPairs) == 0 {
return nil, errors.New("cookiex: legacy encode requires legacy key pairs")
}
keys := make([][32]byte, len(secrets))
for i, secret := range secrets {
key, err := hkdf.Key(sha256.New, secret, nil, kdfInfo, 32)
if err != nil {View on GitHub (pinned to 4174065ffb)
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.
Example fix
// before
codec, err := cookiex.New[Session](cfg.SessionPurpose, keys) // cfg.SessionPurpose = ""
// after
purpose := cfg.SessionPurpose
if purpose == "" { purpose = "session" }
purpose = strings.ReplaceAll(purpose, "|", "_")
codec, err := cookiex.New[Session](purpose, keys) Defensive patterns
Strategy: validation
Validate before calling
// validate purpose before constructing the codec
func validatePurpose(p string) error {
if p == "" || strings.Contains(p, "|") {
return fmt.Errorf("cookie purpose %q must be non-empty and pipe-free", p)
}
return nil
} Try / catch
codec, err := cookiex.New[Session](purpose, secrets)
if err != nil {
log.Fatalf("invalid cookie codec config: %v", err) // fail fast at boot
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- cookiex: at least one secret is required
- cookiex: max age must not be negative
- the provided region is not a valid Ory region
- a key ID must be specified when multiple JWK sets are config
- cookiex: cookie could not be decoded
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/63f4bcf1e3f4f0f9.
Report an issue: GitHub.