Where time matters in Mirror#
| What | Limit | If a clock is wrong |
|---|---|---|
| Login challenge | 2 minutes | Sign-in fails; nothing is exposed |
| Member session | 1 hour at most | It expires early or late by the skew |
| Webhook freshness | 5 minutes; 60 s into the future | A fresh delivery is refused and retried |
| Delegation window | 30 days at most | A desk key starts or stops early or late |
| Tree heads, governance events | Stamped with the signer's clock | Ordering questions; the log itself is hash-linked, not clock-ordered |
| Corridor-key rotation | By corridor version, never by clock | Clock skew cannot split a shipment across keys |
The design keeps every safety property off the wall clock. Verdicts do not depend on it; key rotation does not depend on it. What remains are freshness windows, where a wrong clock causes refusals, not false clears.
Budget and monitoring#
- Budget: keep every Mirror host and member host within 1 second of UTC. The tightest window above (60 s of future skew on a delivery) leaves a wide margin.
- Member side: discipline the clock with NTP against at least three independent sources (or PTP where you already run it), and alert at 500 ms.
- Checking the operator from your side, provably.
POST /timewith a fresh nonce returns the operator's time, signed and bound to that nonce. The operator stamped it somewhere between your send and your receive. So from your own round trip you get an exact interval for the offset: no model of the network, and a replayed old time is refused.mirror-clock.assess(samples, { budget_ms })takes the tightest sample and answers:within_budgetonly when the whole interval fits;out_of_budgetwhen the whole interval lies outside;undeterminedwhen the round trip is too long to tell.
- The SDK's
client.clock()runs it.
- Schedule the check (every 15 minutes is plenty) and alert on anything but
within_budget.
What this does not claim#
- It is a bound against your clock. If your clock is wrong, the bound is against a wrong clock.
- Nothing is claimed about Cloudflare's time-keeping beyond what
/timemeasures. Inside one Worker request, time advances only on I/O, so a signed time is coarse at that grain. - It is not Roughtime: one operator and one signature, not a chain of servers cross-checking each other.