{"record":{"id":"9b6f3abdcac13109","repo":"ory/hydra","slug":"cookiex-at-least-one-secret-is-required","errorCode":null,"errorMessage":"cookiex: at least one secret is required","messagePattern":"cookiex: at least one secret is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/cookiex/cookiex.go","lineNumber":88,"sourceCode":"\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 {\n\t\t\treturn nil, errors.Wrap(err, \"cookiex: cannot derive key\")\n\t\t}\n\t\tkeys[i] = [32]byte(key)","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/cookiex/cookiex.go#L70-L106","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nsecrets := [][]byte{}\ncodec, err := cookiex.New[Session](\"session\", secrets) // panics-free but errors\n// after\nsecrets := [][]byte{[]byte(os.Getenv(\"COOKIE_SECRET\"))}\nif len(secrets[0]) == 0 {\n    log.Fatal(\"COOKIE_SECRET env var must be set\")\n}\ncodec, err := cookiex.New[Session](\"session\", secrets)","handlingStrategy":"validation","validationCode":"// validate secrets before constructing the codec\nfunc validateSecrets(secrets [][]byte) error {\n    if len(secrets) == 0 {\n        return errors.New(\"at least one cookie secret must be configured\")\n    }\n    for i, s := range secrets {\n        if len(s) < 32 {\n            return fmt.Errorf(\"cookie secret %d too short (<32 bytes)\", i)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"codec, err := cookiex.New[Session](purpose, secrets)\nif err != nil {\n    log.Fatalf(\"cookie codec setup failed: %v\", err) // fail fast at boot\n}","preventionTips":["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"],"tags":["cookies","go","configuration","crypto"],"backgroundTag":"missing-secret-key","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"}