{"record":{"id":"e21066118fdfd29e","repo":"can1357/oh-my-pi","slug":"sealed-frame-too-short-e21066","errorCode":null,"errorMessage":"Sealed frame too short","messagePattern":"Sealed frame too short","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/collab-web/src/lib/codec.ts","lineNumber":43,"sourceCode":"\t}\n\treturn crypto.subtle.importKey(\"raw\", asStrict(raw), AES_ALGORITHM, false, [\"encrypt\", \"decrypt\"]);\n}\n\nexport async function seal(key: CryptoKey, frame: WireFrame): 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<WireFrame> {\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 WireFrame;\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":25,"sourceCodeEnd":59,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/collab-web/src/lib/codec.ts#L25-L59","documentation":"open() decrypts a sealed frame produced by seal(), which prefixes an IV before the ciphertext. If the input is not longer than IV_LENGTH there is no room for any ciphertext, so the frame is malformed and decryption cannot proceed; the function throws before touching WebCrypto.","triggerScenarios":"Calling open() with a zero-length or IV-only buffer: receiving an empty WebSocket message, feeding open() an already-decrypted frame, or corrupted transport data truncated to the IV length.","commonSituations":"Relay or socket delivering empty/short frames on reconnect, a peer sending plaintext that is passed to open() by mistake, or version drift where a peer omits the IV prefix.","solutions":["Only pass frames produced by seal() through open(); check the sender path","Log data.byteLength on failure to confirm whether the transport truncated the message","Handle empty socket messages before calling open()","Ensure both peers use the same codec version (IV_LENGTH, AES_ALGORITHM)"],"exampleFix":"// before\nconst frame = await open(key, data); // throws on short buffer\n// after\nif (data.byteLength > IV_LENGTH) {\n  const frame = await open(key, data);\n} else {\n  ignoreMalformedFrame(data);\n}","handlingStrategy":"validation","validationCode":"import { IV_LENGTH } from \"./codec\";\nif (data.byteLength <= IV_LENGTH) {\n  return; // ignore malformed/empty frame\n}","typeGuard":"function isSealedFrame(data: Uint8Array): boolean {\n  return data instanceof Uint8Array && data.byteLength > IV_LENGTH;\n}","tryCatchPattern":"try {\n  const frame = await open(key, data);\n  handleFrame(frame);\n} catch (e) {\n  logger.warn(\"dropping undecryptable frame\", { byteLength: data.byteLength });\n}","preventionTips":["Only feed open() output of seal() from the same codec version","Drop empty socket messages before decryption","Log byteLength on decrypt failures to detect transport truncation","Keep IV_LENGTH/AES_ALGORITHM identical on all peers (same package version)"],"tags":["crypto","decryption","malformed-data","collaboration"],"backgroundTag":"ciphertext-too-short","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}