zeroclaw-labs/zeroclaw · error · anyhow::Error
Request timestamp too old or too far in future
Error message
Request timestamp too old or too far in future
What it means
verify_request is the replay-protection step of ZeroClaw's node transport: before comparing HMAC signatures it checks that the request timestamp is within max_age_secs (300 seconds — a 5-minute window — set in NodeTransport::new) of the receiving node's clock, in either direction. Requests older or further in the future than the window are rejected outright, so stale or pre-signed captured requests cannot be replayed.
Source
Thrown at crates/zeroclaw-runtime/src/nodes/transport.rs:45
})?;
mac.update(×tamp.to_le_bytes());
mac.update(nonce.as_bytes());
mac.update(payload);
Ok(hex::encode(mac.finalize().into_bytes()))
}
/// Verify a signed request, rejecting stale timestamps for replay protection.
pub fn verify_request(
shared_secret: &str,
payload: &[u8],
timestamp: i64,
nonce: &str,
signature: &str,
max_age_secs: i64,
) -> Result<bool> {
let now = Utc::now().timestamp();
if (now - timestamp).abs() > max_age_secs {
bail!("Request timestamp too old or too far in future");
}
let expected = sign_request(shared_secret, payload, timestamp, nonce)?;
Ok(constant_time_eq(expected.as_bytes(), signature.as_bytes()))
}
/// Constant-time comparison to prevent timing attacks.
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
a.iter()
.zip(b.iter())
.fold(0u8, |acc, (x, y)| acc | (x ^ y))
== 0
}
// ── Node transport client ───────────────────────────────────────View on GitHub (pinned to 88bb9c8533)
Solutions
- Sync clocks on both nodes: enable NTP (timedatectl set-ntp true, or run chrony/ntpd) and confirm with timedatectl / date on each side
- Have the sender regenerate and re-sign the request with a fresh timestamp
- If genuine propagation delay exceeds 5 minutes (rare), rebuild the transport with a larger max_request_age_secs — the default is hard-coded at 300 in NodeTransport::new
- If it happens on every request between two specific nodes, compare `date +%s` on both to measure skew directly
Example fix
# diagnose on both nodes date +%s # values must differ by < 300 # fix sudo timedatectl set-ntp true
Defensive patterns
Strategy: validation
Validate before calling
let now = Utc::now().timestamp();
let signed_at = timestamp_used_for_signature; // client side
if (now - signed_at).abs() > max_age_secs {
// do not send: clocks drifted; resync NTP first
} Try / catch
match verify_request(secret, payload, ts, nonce, sig, max_age) {
Err(e) if e.to_string().contains("too old or too far in future") => {
// reject as replay/skew: log both clocks, do NOT widen the window per request
}
other => other,
} Prevention
- Run NTP/chrony on every node and alert on drift beyond ~60s
- Sign immediately before sending (the transport already does) and never cache signed requests
- Monitor this rejection rate — a spike indicates clock or replay problems
- Keep the replay window tight; widen it only with a documented reason, never in response to a single failure
When it happens
Trigger: Client node clock skewed more than 5 minutes from the receiver (VM resumed from snapshot, broken NTP, dual-boot clock offset); replaying a captured request after the window; long queueing between signing and verification so the timestamp ages out.
Common situations: Laptops/VMs with dead RTC batteries or suspended then resumed; containers with drifted clocks; CI environments sending signed node requests after delays; timezone confusion is NOT the cause — these are Unix epoch timestamps.
Related errors
- security.otp.cache_valid_secs must be greater than or equal
- security.otp.gated_actions[{i}] contains invalid characters:
- AgentScopedMemory refuses purge_namespace: cross-agent bulk
- Node request failed: {} {}
- runtime.wasm.tools_dir must not contain '..' path traversal
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/acddd6555f5b1f08.
Report an issue: GitHub.