{"record":{"id":"56a012977e3fd4ab","repo":"crowdsecurity/crowdsec","slug":"keyring-rotation-interval-s-is-below-the-floor-s","errorCode":null,"errorMessage":"keyring rotation interval %s is below the floor %s","messagePattern":"keyring rotation interval (.+?) is below the floor (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/appsec/challenge/keyring.go","lineNumber":103,"sourceCode":"\tmu    sync.RWMutex\n\tcache map[int64][]byte // epoch -> per-epoch sign key\n}\n\n// log returns the component logger (never nil; see the logger field).\nfunc (k *KeyRing) log() *log.Entry {\n\treturn k.logger\n}\n\n// NewKeyRing constructs a KeyRing. masterSecret must be at least minSecretBytes\n// long (callers should already have validated this via WithMasterSecret); the\n// rotation interval must be at least keyringMinRotation. maxLive defaults to\n// keyringDefaultMaxLive when zero.\nfunc NewKeyRing(masterSecret []byte, rotationInterval time.Duration, maxLive int) (*KeyRing, error) {\n\tif len(masterSecret) < minSecretBytes {\n\t\treturn nil, fmt.Errorf(\"keyring master secret is %d bytes; minimum is %d\", len(masterSecret), minSecretBytes)\n\t}\n\tif rotationInterval < keyringMinRotation {\n\t\treturn nil, fmt.Errorf(\"keyring rotation interval %s is below the floor %s\", rotationInterval, keyringMinRotation)\n\t}\n\tif maxLive <= 0 {\n\t\tmaxLive = keyringDefaultMaxLive\n\t}\n\n\treturn &KeyRing{\n\t\tmasterSecret:     masterSecret,\n\t\trotationInterval: rotationInterval,\n\t\tmaxLive:          maxLive,\n\t\tclockSkew:        keyringClockSkew,\n\t\tmasterCookieKey:  deriveMasterCookieKey(masterSecret),\n\t\tlogger:           log.StandardLogger().WithField(\"module\", \"challenge\"),\n\t\tnow:              time.Now,\n\t\tcache:            make(map[int64][]byte),\n\t}, nil\n}\n\n// CurrentEpoch returns the epoch identifier for the current wall-clock time.","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/appsec/challenge/keyring.go#L85-L121","documentation":"NewKeyRing rejects a rotation interval below keyringMinRotation. Cookie-signing keys rotate on this interval; too-fast rotation would create keys faster than the maxLive window can meaningfully retain, breaking verification of recently issued cookies. The constructor fails fast instead of accepting an unusable rotation policy.","triggerScenarios":"Calling NewKeyRing with a rotationInterval smaller than keyringMinRotation (sub-minute/near-zero, e.g. time.Second or 0 via direct construction).","commonSituations":"A config value like `1s` or `0` supplied as the keyring rotation interval; a test constructing a KeyRing with an unrealistically small duration; unit confusion (milliseconds passed as a Duration value).","solutions":["Raise the rotation interval to at least keyringMinRotation (use the documented floor, e.g. minutes)","Check the unit: a raw integer like 500 interpreted as nanoseconds is effectively zero — use time.Duration constants","Leave the value unset so defaults apply if you do not need custom rotation","Adjust tests to use a valid interval and manipulate time rather than shrinking the interval"],"exampleFix":"// before\nkr, err := challenge.NewKeyRing(secret, 500 * time.Millisecond, 0)\n// after\nkr, err := challenge.NewKeyRing(secret, 5 * time.Minute, 0) // >= keyringMinRotation","handlingStrategy":"validation","validationCode":"if rotation < keyringMinRotation { rotation = defaultRotation }\nkr, err := challenge.NewKeyRing(secret, rotation, maxLive)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always build durations with time.Minute / time.Hour constants, not raw integers","Clamp user-supplied rotation values to the documented floor before calling NewKeyRing","Leave rotation unset to accept the default","Add a unit test covering the minimum accepted interval"],"tags":["keyring","configuration","duration"],"backgroundTag":"invalid-config-value","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}