{"record":{"id":"827c141266992f66","repo":"can1357/oh-my-pi","slug":"sealed-frame-too-short","errorCode":null,"errorMessage":"Sealed frame too short","messagePattern":"Sealed frame too short","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/collab/crypto.ts","lineNumber":48,"sourceCode":"\t}\n\treturn crypto.subtle.importKey(\"raw\", asStrict(raw), AES_ALGORITHM, false, [\"encrypt\", \"decrypt\"]);\n}\n\nexport async function seal(key: CryptoKey, frame: CollabFrame): Promise<Uint8Array> {\n\tconst iv = new Uint8Array(IV_LENGTH);\n\tcrypto.getRandomValues(iv);\n\tconst plaintext = TEXT_ENCODER.encode(JSON.stringify(frame));\n\tconst ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: AES_ALGORITHM, iv }, key, plaintext));\n\tconst out = new Uint8Array(IV_LENGTH + ciphertext.byteLength);\n\tout.set(iv, 0);\n\tout.set(ciphertext, IV_LENGTH);\n\treturn out;\n}\n\n/** Inverse of {@link seal}. Throws on auth failure or malformed input. */\nexport async function open(key: CryptoKey, data: Uint8Array): Promise<CollabFrame> {\n\tif (data.byteLength <= IV_LENGTH) {\n\t\tthrow new Error(\"Sealed frame too short\");\n\t}\n\tconst iv = asStrict(data.subarray(0, IV_LENGTH));\n\tconst ciphertext = asStrict(data.subarray(IV_LENGTH));\n\tconst plaintext = new Uint8Array(await crypto.subtle.decrypt({ name: AES_ALGORITHM, iv }, key, ciphertext));\n\treturn JSON.parse(TEXT_DECODER.decode(plaintext)) as CollabFrame;\n}\n\nfunction asStrict(bytes: Uint8Array): Uint8Array<ArrayBuffer> {\n\tif (bytes.buffer instanceof ArrayBuffer && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength) {\n\t\treturn bytes as Uint8Array<ArrayBuffer>;\n\t}\n\tconst copy = new Uint8Array(bytes.byteLength);\n\tcopy.set(bytes);\n\treturn copy;\n}\n","sourceCodeStart":30,"sourceCodeEnd":64,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/collab/crypto.ts#L30-L64","documentation":"open() decrypts a sealed collaboration frame, which is laid out as IV || ciphertext. A frame must be strictly longer than the IV (IV_LENGTH bytes) to contain any ciphertext; anything shorter is malformed (truncated in transit or never produced by seal). Throwing early avoids a confusing WebCrypto 'operation error' from a zero-length ciphertext.","triggerScenarios":"Calling open(key, data) with data.byteLength <= IV_LENGTH: a relay delivers an empty or whitespace-only binary message, a peer's message was truncated by a proxy, or the caller passes raw JSON/plaintext text instead of the sealed binary frame.","commonSituations":"A flaky relay or load balancer drops the tail of a binary websocket message; a custom message handler feeds text frames (or JSON) into open() instead of binary frames; a version mismatch where one peer seals frames with a different layout; tests feed empty Uint8Array buffers.","solutions":["Verify the websocket message is a binary frame from the peer before calling open; skip/handle text frames separately.","Check data.byteLength > IV_LENGTH at the receive site and drop/ignore short frames as corrupt.","Confirm both peers run compatible versions of the collab protocol and that no intermediary truncates binary messages.","If frames are stored/relayed, ensure the full sealed buffer is transmitted (no slicing off the IV prefix)."],"exampleFix":"// before\nconst frame = await open(key, new Uint8Array(await msg.arrayBuffer()));\n// after\nconst bytes = new Uint8Array(await msg.arrayBuffer());\nif (bytes.byteLength <= IV_LENGTH) return; // ignore corrupt frame\nconst frame = await open(key, bytes);","handlingStrategy":"validation","validationCode":"if (!(data instanceof Uint8Array) || data.byteLength <= IV_LENGTH) return; // skip corrupt/empty frame\nconst frame = await open(key, data);","typeGuard":"function isOpenableFrame(data: unknown): data is Uint8Array {\n  return data instanceof Uint8Array && data.byteLength > IV_LENGTH;\n}","tryCatchPattern":"try {\n  const frame = await open(key, data);\n} catch {\n  // treat as corrupt frame: log and ignore; peers authenticate via GCM anyway\n}","preventionTips":["Route only binary websocket frames into open(); handle text frames separately.","Never strip or re-encode the IV prefix when relaying frames.","Keep collab protocol versions in sync across host and guests."],"tags":["crypto","protocol","websocket"],"backgroundTag":"malformed-packet","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}