ory/hydra · error
cookiex: at least one secret is required
Error message
cookiex: at least one secret is required
What it means
cookiex.New requires at least one secret because the codec seals with a key derived from the first secret and opens with keys derived from all of them. With an empty secrets slice there is no key material, so the constructor immediately returns this error.
Source
Thrown at oryx/cookiex/cookiex.go:88
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 {
return nil, errors.Wrap(err, "cookiex: cannot derive key")
}
keys[i] = [32]byte(key)View on GitHub (pinned to 4174065ffb)
Solutions
- Provide at least one secret (ideally 32+ random bytes) at startup, e.g. generate with crypto/rand and store in a secret manager.
- Fail fast at boot: validate the secrets config before constructing the codec and surface a clear config error.
- Seed from the existing system secret (e.g. Hydra's system secret) if the app should share key material.
- In tests, always pass a dummy secret slice to New.
Example fix
// before
secrets := [][]byte{}
codec, err := cookiex.New[Session]("session", secrets) // panics-free but errors
// after
secrets := [][]byte{[]byte(os.Getenv("COOKIE_SECRET"))}
if len(secrets[0]) == 0 {
log.Fatal("COOKIE_SECRET env var must be set")
}
codec, err := cookiex.New[Session]("session", secrets) Defensive patterns
Strategy: validation
Validate before calling
// validate secrets before constructing the codec
func validateSecrets(secrets [][]byte) error {
if len(secrets) == 0 {
return errors.New("at least one cookie secret must be configured")
}
for i, s := range secrets {
if len(s) < 32 {
return fmt.Errorf("cookie secret %d too short (<32 bytes)", i)
}
}
return nil
} Try / catch
codec, err := cookiex.New[Session](purpose, secrets)
if err != nil {
log.Fatalf("cookie codec setup failed: %v", err) // fail fast at boot
} Prevention
- Fail fast at startup if cookie secrets config is empty
- Generate secrets with crypto/rand and store them in a secret manager
- Add a CI/config test that constructs the codec from production-shaped config
- Document required env vars (e.g. COOKIE_SECRET) in deployment docs
When it happens
Trigger: Calling New[T](purpose, nil) or New[T](purpose, [][]byte{}) — typically when secrets come from config/environment and the list is empty (oryx/cookiex.go:88).
Common situations: Missing or empty SESSION_COOKIE_SECRETS-like env var; secrets YAML key present but with no items; a migration that changed the config schema so the loader returns an empty slice; tests constructing a codec before injecting test secrets.
Related errors
- cookiex: purpose must be non-empty and must not contain a pi
- cookiex: cookie could not be decoded
- cookiex: max age must not be negative
- cookiex: legacy encode requires legacy key pairs
- invalid elliptic curve key size, this algorithm does not sup
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/9b6f3abdcac13109.
Report an issue: GitHub.