sync-limits.ts
1// Sync trust + resource bounds — audit fixes HV-1 (timestamp clamp) and2// MV-3 (receive-path size bounds). Shared between the background sync3// stack (vault.ts, chirp-sync-vault-adapter.ts, chirp-sync-coordinator.ts)4// and the transport (shared/chirp-sync-transport.ts), so it lives in shared/.56/**7 * HV-1: maximum tolerated forward clock skew on any timestamp received from8 * a sync peer. Matches the SYNC_HELLO skew warning window's intent — a peer9 * whose clock is slightly ahead still merges cleanly, but a deliberately10 * far-future `modifiedAt`/`deletedAt` can no longer permanently pin a11 * malicious value against honest local edits.12 */13export const MAX_CLOCK_SKEW_MS = 5 * 60 * 1000;1415/**16 * Clamp a peer-supplied timestamp to `now + MAX_CLOCK_SKEW_MS`. Applied to17 * every remote `modifiedAt` and `deletedAt` BEFORE it is compared against18 * any local timestamp (HV-1). A clamped far-future timestamp loses to any19 * local edit made more than MAX_CLOCK_SKEW_MS from now, and the monotonic20 * local entry clock (vault.ts monotonicEntryTimestamp) can always advance21 * past it.22 */23export function clampSyncTimestamp(ts: number, now: number = Date.now()): number {24 return Math.min(ts, now + MAX_CLOCK_SKEW_MS);25}2627/**28 * MV-3: hard resource bounds on the sync receive path. A paired/vouched29 * peer (or anything that passes the Noise XX static-key pin) must not be30 * able to OOM the service worker with one frame or one giant manifest.31 */32export const SYNC_LIMITS = {33 /** Max bytes per decrypted Noise frame, checked before JSON.parse. */34 MAX_FRAME_BYTES: 50 * 1024 * 1024, // 50 MiB35 /** Max entries accepted in one SYNC_ENTRIES / manifest payload. */36 MAX_ENTRY_COUNT: 100_000,37 /** Max plaintext size of one decrypted entry envelope. */38 MAX_ENTRY_PLAINTEXT_BYTES: 512 * 1024, // 512 KiB39 /** Max tombstones accepted in one SYNC_TOMBSTONES payload. */40 MAX_TOMBSTONE_COUNT: 100_000,41} as const;