9d36d625ca49

style: align sync reassembly deadline flow

time
2026-09-07 01:02 UTC · 1 month ago
machine
koh2
agent
gpt-5
session
red team remediation
parent
69197437712d
1 file src/shared/sync-transport.ts+41 −41
src/shared/sync-transport.ts+41 −41
@@ -542,52 +542,52 @@542542           const ct = await (deadline543543             ? Promise.race([receiveCiphertext(), deadline])544544             : receiveCiphertext());545-        const pt = session.decrypt(ct);546-        if (buffered === null && pt.byteLength > 0 && pt[0] === FRAME_CONTINUATION_MARKER) {547-          // Strip the continuation byte and keep the payload.548-          buffered = pt.subarray(1);549-          deadline = new Promise<never>((_resolve, reject) => {550-            deadlineTimer = setTimeout(() => {551-              session.close();552-              try { ws.close(1008, "sync frame timeout"); } catch { /* ignore */ }553-              reject(new SyncFrameReassemblyTimeoutError());554-            }, reassemblyTimeoutMs);555-          });556-          continue;557-        }558-        if (buffered !== null) {559-          if (pt.byteLength > 0 && pt[0] === FRAME_CONTINUATION_MARKER) {560-            const merged: Uint8Array = new Uint8Array(buffered.length + pt.byteLength - 1);561-            merged.set(buffered, 0);562-            merged.set(pt.subarray(1), buffered.length);563-            buffered = merged;564-            // PB-26-002: the frame bound must hold DURING reassembly. The565-            // post-receive check in the coordinator runs only after the full566-            // frame is in memory, so a peer that completes the Noise KK567-            // handshake could otherwise stream continuation chunks without568-            // bound inside the session window.569-            if (buffered.length > SYNC_LIMITS.MAX_FRAME_BYTES) {545+          const pt = session.decrypt(ct);546+          if (buffered === null && pt.byteLength > 0 && pt[0] === FRAME_CONTINUATION_MARKER) {547+            // Strip the continuation byte and keep the payload.548+            buffered = pt.subarray(1);549+            deadline = new Promise<never>((_resolve, reject) => {550+              deadlineTimer = setTimeout(() => {551+                session.close();552+                try { ws.close(1008, "sync frame timeout"); } catch { /* ignore */ }553+                reject(new SyncFrameReassemblyTimeoutError());554+              }, reassemblyTimeoutMs);555+            });556+            continue;557+          }558+          if (buffered !== null) {559+            if (pt.byteLength > 0 && pt[0] === FRAME_CONTINUATION_MARKER) {560+              const merged: Uint8Array = new Uint8Array(buffered.length + pt.byteLength - 1);561+              merged.set(buffered, 0);562+              merged.set(pt.subarray(1), buffered.length);563+              buffered = merged;564+              // PB-26-002: the frame bound must hold DURING reassembly. The565+              // post-receive check in the coordinator runs only after the full566+              // frame is in memory, so a peer that completes the Noise KK567+              // handshake could otherwise stream continuation chunks without568+              // bound inside the session window.569+              if (buffered.length > SYNC_LIMITS.MAX_FRAME_BYTES) {570+                throw new SyncFrameTooLargeError(571+                  `sync frame exceeds ${SYNC_LIMITS.MAX_FRAME_BYTES} bytes`,572+                );573+              }574+              continue;575+            }576+            // Final chunk. PB-26-602: the continuation loop only bounds what is577+            // buffered, so the last chunk could push the frame up to 64 KiB past578+            // the cap. Bound the concatenation, not just the buffer.579+            if (buffered.length + pt.byteLength > SYNC_LIMITS.MAX_FRAME_BYTES) {570580               throw new SyncFrameTooLargeError(571581                 `sync frame exceeds ${SYNC_LIMITS.MAX_FRAME_BYTES} bytes`,572582               );573583             }574-            continue;584+            const reassembled = new Uint8Array(buffered.length + pt.byteLength);585+            reassembled.set(buffered, 0);586+            reassembled.set(pt, buffered.length);587+            return unpadSyncFrame(reassembled);575588           }576-          // Final chunk. PB-26-602: the continuation loop only bounds what is577-          // buffered, so the last chunk could push the frame up to 64 KiB past578-          // the cap. Bound the concatenation, not just the buffer.579-          if (buffered.length + pt.byteLength > SYNC_LIMITS.MAX_FRAME_BYTES) {580-            throw new SyncFrameTooLargeError(581-              `sync frame exceeds ${SYNC_LIMITS.MAX_FRAME_BYTES} bytes`,582-            );583-          }584-          const reassembled = new Uint8Array(buffered.length + pt.byteLength);585-          reassembled.set(buffered, 0);586-          reassembled.set(pt, buffered.length);587-          return unpadSyncFrame(reassembled);588-        }589-        // Single-chunk frame, exactly as before.590-        return unpadSyncFrame(pt);589+          // Single-chunk frame, exactly as before.590+          return unpadSyncFrame(pt);591591         }592592       } finally {593593         if (deadlineTimer !== undefined) clearTimeout(deadlineTimer);

view file