{"record":{"id":"8c706ea392dea88c","repo":"larksuite/cli","slug":"timestamp-drift-0fs-exceeds-limit-ds","errorCode":null,"errorMessage":"timestamp drift %.0fs exceeds limit %ds","messagePattern":"timestamp drift %\\.0fs exceeds limit (.+?)s","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sidecar/hmac.go","lineNumber":76,"sourceCode":"\n// Sign computes the HMAC-SHA256 signature over the canonical request string.\nfunc Sign(key []byte, req CanonicalRequest) string {\n\tmac := hmac.New(sha256.New, key)\n\tmac.Write([]byte(req.canonicalString()))\n\treturn hex.EncodeToString(mac.Sum(nil))\n}\n\n// Verify checks that signature matches the HMAC-SHA256 of the canonical\n// request and that the timestamp is within MaxTimestampDrift seconds of now.\n// Returns nil on success.\nfunc Verify(key []byte, req CanonicalRequest, signature string) error {\n\tts, err := strconv.ParseInt(req.Timestamp, 10, 64)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid timestamp %q: %w\", req.Timestamp, err)\n\t}\n\tdrift := math.Abs(float64(time.Now().Unix() - ts))\n\tif drift > MaxTimestampDrift {\n\t\treturn fmt.Errorf(\"timestamp drift %.0fs exceeds limit %ds\", drift, MaxTimestampDrift)\n\t}\n\texpected := Sign(key, req)\n\tif !hmac.Equal([]byte(expected), []byte(signature)) {\n\t\treturn fmt.Errorf(\"HMAC signature mismatch\")\n\t}\n\treturn nil\n}\n\n// Timestamp returns the current Unix epoch seconds as a string.\nfunc Timestamp() string {\n\treturn strconv.FormatInt(time.Now().Unix(), 10)\n}\n","sourceCodeStart":58,"sourceCodeEnd":89,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/sidecar/hmac.go#L58-L89","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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)."],"exampleFix":"// before\nreq.Timestamp = cachedTimestamp // signed once at startup\n// after\nreq.Timestamp = sidecar.Timestamp() // fresh epoch seconds per request/retry","handlingStrategy":"validation","validationCode":"ts, err := strconv.ParseInt(req.Timestamp, 10, 64)\nif err != nil {\n\treturn fmt.Errorf(\"bad timestamp: %w\", err)\n}\ndrift := math.Abs(float64(time.Now().Unix() - ts))\nif drift > sidecar.MaxTimestampDrift {\n\treturn fmt.Errorf(\"client clock off by %.0fs; sync NTP before calling\", drift)\n}","typeGuard":"func timestampFresh(req sidecar.CanonicalRequest) bool {\n\tts, err := strconv.ParseInt(req.Timestamp, 10, 64)\n\tif err != nil {\n\t\treturn false\n\t}\n\td := math.Abs(float64(time.Now().Unix() - ts))\n\treturn d <= sidecar.MaxTimestampDrift\n}","tryCatchPattern":"if err := sidecar.Verify(key, req, sig); err != nil {\n\tif strings.Contains(err.Error(), \"timestamp drift\") {\n\t\t// clock skew: resync clock, refresh timestamp, re-sign and retry once\n\t\treq.Timestamp = sidecar.Timestamp()\n\t\tsig := sidecar.Sign(key, req)\n\t\terr = sidecar.Verify(key, req, sig)\n\t}\n\treturn err\n}","preventionTips":["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."],"tags":["hmac","timestamp","clock-skew","sidecar","replay-protection"],"backgroundTag":"clock-skew-exceeded","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}