larksuite/cli · error
timestamp drift %.0fs exceeds limit %ds
Error message
timestamp drift %.0fs exceeds limit %ds
What it means
Verify() in the sidecar package checks that a signed request's Timestamp is within MaxTimestampDrift (60s) of the server's current time before validating the HMAC. This error means the absolute difference exceeded 60 seconds, so the request is rejected as a replay/stale request. The timestamp check is an anti-replay window; it runs before signature comparison.
Source
Thrown at sidecar/hmac.go:76
// Sign computes the HMAC-SHA256 signature over the canonical request string.
func Sign(key []byte, req CanonicalRequest) string {
mac := hmac.New(sha256.New, key)
mac.Write([]byte(req.canonicalString()))
return hex.EncodeToString(mac.Sum(nil))
}
// Verify checks that signature matches the HMAC-SHA256 of the canonical
// request and that the timestamp is within MaxTimestampDrift seconds of now.
// Returns nil on success.
func Verify(key []byte, req CanonicalRequest, signature string) error {
ts, err := strconv.ParseInt(req.Timestamp, 10, 64)
if err != nil {
return fmt.Errorf("invalid timestamp %q: %w", req.Timestamp, err)
}
drift := math.Abs(float64(time.Now().Unix() - ts))
if drift > MaxTimestampDrift {
return fmt.Errorf("timestamp drift %.0fs exceeds limit %ds", drift, MaxTimestampDrift)
}
expected := Sign(key, req)
if !hmac.Equal([]byte(expected), []byte(signature)) {
return fmt.Errorf("HMAC signature mismatch")
}
return nil
}
// Timestamp returns the current Unix epoch seconds as a string.
func Timestamp() string {
return strconv.FormatInt(time.Now().Unix(), 10)
}
View on GitHub (pinned to 7fd6ef3c07)
Solutions
- Call sidecar.Timestamp() (or time.Now().Unix()) immediately before signing each request, and re-sign on any retry.
- Fix the machine's clock: run NTP/chrony (e.g. `sudo sntp -sS pool.ntp.org` or enable time sync in the VM/container runtime).
- Ensure the timestamp is Unix epoch SECONDS, not milliseconds — divide a ms value by 1000.
- Check drift by comparing `time.Now().Unix()` on both sides; if skew is structural, sync the sandbox host clock, since the 60s limit is a fixed constant (MaxTimestampDrift).
Example fix
// before req.Timestamp = cachedTimestamp // signed once at startup // after req.Timestamp = sidecar.Timestamp() // fresh epoch seconds per request/retry
Defensive patterns
Strategy: validation
Validate before calling
ts, err := strconv.ParseInt(req.Timestamp, 10, 64)
if err != nil {
return fmt.Errorf("bad timestamp: %w", err)
}
drift := math.Abs(float64(time.Now().Unix() - ts))
if drift > sidecar.MaxTimestampDrift {
return fmt.Errorf("client clock off by %.0fs; sync NTP before calling", drift)
} Type guard
func timestampFresh(req sidecar.CanonicalRequest) bool {
ts, err := strconv.ParseInt(req.Timestamp, 10, 64)
if err != nil {
return false
}
d := math.Abs(float64(time.Now().Unix() - ts))
return d <= sidecar.MaxTimestampDrift
} Try / catch
if err := sidecar.Verify(key, req, sig); err != nil {
if strings.Contains(err.Error(), "timestamp drift") {
// clock skew: resync clock, refresh timestamp, re-sign and retry once
req.Timestamp = sidecar.Timestamp()
sig := sidecar.Sign(key, req)
err = sidecar.Verify(key, req, sig)
}
return err
} Prevention
- Always call sidecar.Timestamp() at sign time, never cache the timestamp across retries.
- Re-sign on every retry attempt.
- Run NTP/chrony in sandboxes, containers, and VMs; alert on clock drift.
- Use epoch seconds, not milliseconds.
When it happens
Trigger: Calling sidecar.Verify (directly or via ServeHTTP / verifyWithClientKeys) with a CanonicalRequest whose Timestamp string parses to a Unix time more than 60 seconds away from time.Now() on the verifying side — including timestamps in the future.
Common situations: Client clock skew (VM/container clock drift, suspended laptop, VM resuming from snapshot); signing a request and retrying it more than a minute later; manually constructed requests reusing an old cached timestamp; timezone-naive code building the timestamp in non-epoch units (e.g. milliseconds, which makes drift astronomically large).
Related errors
- invalid timestamp %q: %w
- HMAC signature mismatch
- cannot parse %q as ISO 8601
- invalid timestamp %q: %w
- invalid timestamp %q: %w
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/8c706ea392dea88c.
Report an issue: GitHub.