{"record":{"id":"ef3b40269446c69a","repo":"zeroclaw-labs/zeroclaw","slug":"security-otp-cache-valid-secs-must-be-greater-than","errorCode":null,"errorMessage":"security.otp.cache_valid_secs must be greater than or equal to security.otp.token_ttl_secs","messagePattern":"security\\.otp\\.cache_valid_secs must be greater than or equal to security\\.otp\\.token_ttl_secs","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/schema.rs","lineNumber":21396,"sourceCode":"                \"security.otp.challenge_max_attempts must be greater than 0\"\n            );\n        }\n        if self.security.otp.token_ttl_secs == 0 {\n            validation_bail!(\n                InvalidNumericRange,\n                \"security.otp.token_ttl_secs\",\n                \"security.otp.token_ttl_secs must be greater than 0\"\n            );\n        }\n        if self.security.otp.cache_valid_secs == 0 {\n            validation_bail!(\n                InvalidNumericRange,\n                \"security.otp.cache_valid_secs\",\n                \"security.otp.cache_valid_secs must be greater than 0\"\n            );\n        }\n        if self.security.otp.cache_valid_secs < self.security.otp.token_ttl_secs {\n            anyhow::bail!(\n                \"security.otp.cache_valid_secs must be greater than or equal to security.otp.token_ttl_secs\"\n            );\n        }\n        if self.security.otp.challenge_max_attempts == 0 {\n            validation_bail!(\n                InvalidNumericRange,\n                \"security.otp.challenge_max_attempts\",\n                \"security.otp.challenge_max_attempts must be greater than 0\"\n            );\n        }\n        for (i, action) in self.security.otp.gated_actions.iter().enumerate() {\n            let normalized = action.trim();\n            if normalized.is_empty() {\n                validation_bail!(\n                    RequiredFieldEmpty,\n                    format!(\"security.otp.gated_actions[{i}]\"),\n                    \"security.otp.gated_actions[{i}] must not be empty\"\n                );","sourceCodeStart":21378,"sourceCodeEnd":21414,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/schema.rs#L21378-L21414","documentation":"ZeroClaw requires security.otp.cache_valid_secs to be greater than or equal to security.otp.token_ttl_secs so the OTP cache always outlives the tokens it caches — a shorter cache window would still be legal, but this invariant is enforced to keep cached entries valid for the token's whole lifetime. The comparison runs after the separate `cache_valid_secs > 0` guard, so a zero value fails earlier with a different message. Equality is allowed (>=).","triggerScenarios":"Configure e.g. `token_ttl_secs = 300` with `cache_valid_secs = 60`. Any combination where the cache window is strictly smaller than the token TTL bails during security validation.","commonSituations":"Hardening passes that shrink cache windows to limit replay risk; adopting a 30s TOTP step for token_ttl while leaving a stale small cache value; env-mirror overrides that set only one of the two keys.","solutions":["Raise cache_valid_secs to at least token_ttl_secs (e.g. cache_valid_secs = 300, token_ttl_secs = 300)","Or lower token_ttl_secs below the cache window if shorter-lived tokens are acceptable","Keep both keys adjacent in the [security.otp] block so they are edited together","Remember cache_valid_secs must also be > 0 — zero fails an earlier check with its own message"],"exampleFix":"# before\n[security.otp]\ncache_valid_secs = 60\ntoken_ttl_secs = 300\n\n# after\n[security.otp]\ncache_valid_secs = 300\ntoken_ttl_secs = 300","handlingStrategy":"validation","validationCode":"fn otp_ttl_precheck(otp: &zeroclaw_config::OtpConfig) -> Result<(), String> {\n    if otp.cache_valid_secs == 0 {\n        return Err(\"security.otp.cache_valid_secs must be greater than 0\".into());\n    }\n    if otp.cache_valid_secs < otp.token_ttl_secs {\n        return Err(\"cache_valid_secs must be >= token_ttl_secs\".into());\n    }\n    Ok(())\n}","typeGuard":"fn otp_windows_consistent(otp: &zeroclaw_config::OtpConfig) -> bool {\n    otp.cache_valid_secs > 0 && otp.cache_valid_secs >= otp.token_ttl_secs\n}","tryCatchPattern":"if let Err(err) = config.validate() {\n    if err.to_string().contains(\"cache_valid_secs must be greater than or equal\") {\n        // bump cache_valid_secs to token_ttl_secs and re-validate\n    }\n}","preventionTips":["Derive cache_valid_secs from token_ttl_secs in config templates (e.g. ttl * 5) instead of hardcoding both","Document the invariant next to the keys in shared configs","Add a unit test that asserts the invariant for every shipped profile"],"tags":["configuration","validation","security","otp","zeroclaw","rust"],"backgroundTag":"cache-ttl-mismatch","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}