zeroclaw-labs/zeroclaw · error

security.otp.cache_valid_secs must be greater than or equal

Error message

security.otp.cache_valid_secs must be greater than or equal to security.otp.token_ttl_secs

What it means

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 (>=).

Source

Thrown at crates/zeroclaw-config/src/schema.rs:21396

                "security.otp.challenge_max_attempts must be greater than 0"
            );
        }
        if self.security.otp.token_ttl_secs == 0 {
            validation_bail!(
                InvalidNumericRange,
                "security.otp.token_ttl_secs",
                "security.otp.token_ttl_secs must be greater than 0"
            );
        }
        if self.security.otp.cache_valid_secs == 0 {
            validation_bail!(
                InvalidNumericRange,
                "security.otp.cache_valid_secs",
                "security.otp.cache_valid_secs must be greater than 0"
            );
        }
        if self.security.otp.cache_valid_secs < self.security.otp.token_ttl_secs {
            anyhow::bail!(
                "security.otp.cache_valid_secs must be greater than or equal to security.otp.token_ttl_secs"
            );
        }
        if self.security.otp.challenge_max_attempts == 0 {
            validation_bail!(
                InvalidNumericRange,
                "security.otp.challenge_max_attempts",
                "security.otp.challenge_max_attempts must be greater than 0"
            );
        }
        for (i, action) in self.security.otp.gated_actions.iter().enumerate() {
            let normalized = action.trim();
            if normalized.is_empty() {
                validation_bail!(
                    RequiredFieldEmpty,
                    format!("security.otp.gated_actions[{i}]"),
                    "security.otp.gated_actions[{i}] must not be empty"
                );

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Raise cache_valid_secs to at least token_ttl_secs (e.g. cache_valid_secs = 300, token_ttl_secs = 300)
  2. Or lower token_ttl_secs below the cache window if shorter-lived tokens are acceptable
  3. Keep both keys adjacent in the [security.otp] block so they are edited together
  4. Remember cache_valid_secs must also be > 0 — zero fails an earlier check with its own message

Example fix

# before
[security.otp]
cache_valid_secs = 60
token_ttl_secs = 300

# after
[security.otp]
cache_valid_secs = 300
token_ttl_secs = 300
Defensive patterns

Strategy: validation

Validate before calling

fn otp_ttl_precheck(otp: &zeroclaw_config::OtpConfig) -> Result<(), String> {
    if otp.cache_valid_secs == 0 {
        return Err("security.otp.cache_valid_secs must be greater than 0".into());
    }
    if otp.cache_valid_secs < otp.token_ttl_secs {
        return Err("cache_valid_secs must be >= token_ttl_secs".into());
    }
    Ok(())
}

Type guard

fn otp_windows_consistent(otp: &zeroclaw_config::OtpConfig) -> bool {
    otp.cache_valid_secs > 0 && otp.cache_valid_secs >= otp.token_ttl_secs
}

Try / catch

if let Err(err) = config.validate() {
    if err.to_string().contains("cache_valid_secs must be greater than or equal") {
        // bump cache_valid_secs to token_ttl_secs and re-validate
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/ef3b40269446c69a. Report an issue: GitHub.