// Sync trust + resource bounds — audit fixes HV-1 (timestamp clamp) and // MV-3 (receive-path size bounds). Shared between the background sync // stack (vault.ts, sync-vault-adapter.ts, sync-coordinator.ts) // and the transport (shared/sync-transport.ts), so it lives in shared/. /** * HV-1: maximum tolerated forward clock skew on any timestamp received from * a sync peer. Matches the SYNC_HELLO skew warning window's intent — a peer * whose clock is slightly ahead still merges cleanly, but a deliberately * far-future `modifiedAt`/`deletedAt` can no longer permanently pin a * malicious value against honest local edits. */ export const MAX_CLOCK_SKEW_MS = 5 * 60 * 1000; /** * Clamp a peer-supplied timestamp to `now + MAX_CLOCK_SKEW_MS`. Applied to * every remote `modifiedAt` and `deletedAt` BEFORE it is compared against * any local timestamp (HV-1). A clamped far-future timestamp loses to any * local edit made more than MAX_CLOCK_SKEW_MS from now, and the monotonic * local entry clock (vault.ts monotonicEntryTimestamp) can always advance * past it. */ export function clampSyncTimestamp(ts: number, now: number = Date.now()): number { return Math.min(ts, now + MAX_CLOCK_SKEW_MS); } /** * MV-3: hard resource bounds on the sync receive path. A paired/vouched * peer (or anything that passes the Noise NK static-key pin) must not be * able to OOM the service worker with one frame or one giant manifest. */ export const SYNC_LIMITS = { /** * Max bytes per WebSocket/Noise frame. Cloudflare Workers closes messages * above 32 MiB; 24 MiB leaves transport overhead and platform margin. */ MAX_FRAME_BYTES: 24 * 1024 * 1024, // 24 MiB /** Max entries accepted in one SYNC_ENTRIES / manifest payload. */ MAX_ENTRY_COUNT: 100_000, /** Max plaintext size of one decrypted entry envelope. */ MAX_ENTRY_PLAINTEXT_BYTES: 512 * 1024, // 512 KiB /** Max tombstones accepted in one SYNC_TOMBSTONES payload. */ MAX_TOMBSTONE_COUNT: 100_000, } as const;