Pumpkin-MC/Pumpkin · warning · TelemetryVerificationError

Timestamp drift exceeded: drift was

Error message

Timestamp drift exceeded: drift was {drift}s (max allowed is {MAX_CLOCK_DRIFT_SECS}s)

What it means

TelemetryVerificationError::ClockDriftExceeded { drift } is returned during telemetry signature verification when the request's timestamp is valid but differs from the server's current time by more than MAX_CLOCK_DRIFT_SECS. The drift value in the error reports the actual observed difference. This replay-protection check rejects stale or future-dated requests even when their signature is otherwise valid.

Solutions

  1. Enable NTP/time sync on the client and server machines so their clocks agree
  2. Regenerate and resend the request with a fresh timestamp instead of retrying a stale signed request
  3. If offline buffering is intentional, flush telemetry before it exceeds the MAX_CLOCK_DRIFT_SECS window
  4. Reduce clock skew in containers by syncing the host clock and avoiding paused VMs/snapshots with stale time

Example fix

// before
let ts = cached_timestamp; // minutes old, signature reuse
// after
let ts = Utc::now(); // re-sign with fresh timestamp before sending
let sig = sign_request(&key, &body, &ts);
Defensive patterns

Strategy: retry

Validate before calling

fn drift_ok(sent_at: chrono::DateTime<Utc>, max: u64) -> bool {
    (Utc::now() - sent_at).num_seconds().unsigned_abs() <= max
}
// call before signing: assert!(drift_ok(now, MAX_CLOCK_DRIFT_SECS))

Try / catch

match verify_signature(&req) {
    Err(TelemetryVerificationError::ClockDriftExceeded { drift }) => {
        info!("request drifted {drift}s; asking client to re-sign with fresh timestamp");
        retry_with_fresh_timestamp();
    }
    other => other?,
}

Prevention

When it happens

Trigger: A signed telemetry request arrives with a timestamp older/newer than the allowed window: the client machine's clock is skewed, a request was queued/retried past the window, or a captured request is replayed later.

Common situations: Servers or clients with unsynchronized clocks (no NTP); long network queues or offline buffering of telemetry that is sent too late; container hosts whose clocks drift; deliberate replay attempts.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of Pumpkin-MC/Pumpkin@8d4639e25a (2026-09-09). Data as JSON: /api/errors/140dc71c30e239d4. Report an issue: GitHub.

Appendix: source

Thrown at crates/pumpkin/src/telemetry.rs:123

#[must_use]
pub fn sign_telemetry_payload(
    signing_key: &SigningKey,
    timestamp_str: &str,
    body_bytes: &[u8],
) -> (String, String) {
    let signed_data = compute_signed_data(timestamp_str, body_bytes);
    let signature = signing_key.sign(&signed_data);
    let pubkey_hex = hex::encode(signing_key.verifying_key().to_bytes());
    let sig_hex = hex::encode(signature.to_bytes());
    (pubkey_hex, sig_hex)
}

/// Errors that can occur during telemetry request signature verification.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum TelemetryVerificationError {
    #[error("Invalid timestamp string format")]
    InvalidTimestamp,
    #[error(
        "Timestamp drift exceeded: drift was {drift}s (max allowed is {MAX_CLOCK_DRIFT_SECS}s)"
    )]
    ClockDriftExceeded { drift: u64 },
    #[error("Invalid public key hex encoding")]
    InvalidPublicKeyHex,
    #[error("Invalid public key bytes: {0}")]
    InvalidPublicKey(String),
    #[error("Invalid signature hex encoding")]
    InvalidSignatureHex,
    #[error("Invalid signature bytes: {0}")]
    InvalidSignature(String),
    #[error("Signature verification failed: {0}")]
    VerificationFailed(String),
}

/// Verifies a signed telemetry request against an Ed25519 public key and timestamp.
///
/// Ensures clock drift between `current_time_secs` and `timestamp_str` does not exceed `±300` seconds.

View on GitHub (pinned to 8d4639e25a)