{"record":{"id":"0fd9591567b81c51","repo":"ruvnet/ruflo","slug":"decryptbuffer-blob-too-short-blob-length-b-ne","errorCode":null,"errorMessage":"decryptBuffer: blob too short (${blob.length}B; need >= ${MIN_BLOB_LEN}B)","messagePattern":"decryptBuffer: blob too short \\((.+?)B; need >= (.+?)B\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/encryption/vault.ts","lineNumber":155,"sourceCode":" * Decrypt a wire-format blob. Verifies the magic byte (sanity), parses\n * iv + ciphertext + tag, runs AES-256-GCM decrypt, and lets the GCM\n * auth tag fail loudly on tamper (Node throws \"Unsupported state or\n * unable to authenticate data\" — we let that propagate).\n *\n * Pre-condition: caller has already determined this is an encrypted\n * blob via isEncryptedBlob(). decryptBuffer throws on bad magic so a\n * mistaken plaintext blob still fails loudly rather than producing\n * garbage.\n */\nexport function decryptBuffer(blob: Buffer, key: Buffer): Buffer {\n  if (!Buffer.isBuffer(blob)) {\n    throw new TypeError('decryptBuffer: blob must be a Buffer');\n  }\n  if (!Buffer.isBuffer(key) || key.length !== KEY_LEN) {\n    throw new TypeError(`decryptBuffer: key must be a ${KEY_LEN}-byte Buffer`);\n  }\n  if (blob.length < MIN_BLOB_LEN) {\n    throw new Error(\n      `decryptBuffer: blob too short (${blob.length}B; need >= ${MIN_BLOB_LEN}B)`,\n    );\n  }\n  const magic = blob.subarray(0, MAGIC_LEN);\n  // timingSafeEqual to avoid an oracle on the magic bytes specifically;\n  // not strictly required (the magic isn't secret) but cheap and correct.\n  if (!timingSafeEqual(magic, MAGIC)) {\n    throw new Error(\n      'decryptBuffer: bad magic — blob is not Ruflo-encrypted (RFE1)',\n    );\n  }\n  const iv = blob.subarray(MAGIC_LEN, MAGIC_LEN + IV_LEN);\n  const tag = blob.subarray(blob.length - TAG_LEN);\n  const ciphertext = blob.subarray(MAGIC_LEN + IV_LEN, blob.length - TAG_LEN);\n\n  const decipher = createDecipheriv(ALG, key, iv);\n  decipher.setAuthTag(tag);\n  return Buffer.concat([decipher.update(ciphertext), decipher.final()]);","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/encryption/vault.ts#L137-L173","documentation":"Thrown by decryptBuffer() when blob.length < MIN_BLOB_LEN (MAGIC_LEN + IV_LEN + TAG_LEN). A blob shorter than the minimum cannot contain the header/footer fixed fields, so it is structurally invalid — attempting to slice it would read out of bounds and produce nonsense. Use isEncryptedBlob() to test first; this path assumes the caller already believes the blob is encrypted.","triggerScenarios":"Passing a truncated file (partial download, log-rotation cutoff), an empty buffer, a plaintext buffer that the caller mistook for ciphertext, or a buffer whose magic was stripped.","commonSituations":"Reading a secret file that was partially written or corrupted, passing the wrong file to the vault, a buffer that is actually plaintext (and should skip decryption), or a copy/transfer that truncated the blob.","solutions":["Call isEncryptedBlob(blob) before decryptBuffer; if it returns false, treat the value as plaintext (or refuse).","Verify the source file's size against the expected minimum before reading.","Re-emit/re-download the blob if it was truncated in transit."],"exampleFix":"// before\ndecryptBuffer(buf, key)   // buf was truncated\n// after\nimport { isEncryptedBlob } from '../encryption/vault.js';\nif (!isEncryptedBlob(buf)) {\n  // not vault-encrypted — handle as plaintext or refuse\n  throw new Error('blob is not vault-encrypted');\n}\ndecryptBuffer(buf, key);","handlingStrategy":"type-guard","validationCode":"import { isEncryptedBlob } from '../encryption/vault.js';\nfunction safeDecrypt(buf: Buffer, key: Buffer): Buffer {\n  if (!isEncryptedBlob(buf)) {\n    throw new Error('blob is not vault-encrypted (too short or wrong magic)');\n  }\n  return decryptBuffer(buf, key);\n}","typeGuard":"import { isEncryptedBlob } from '../encryption/vault.js';\n// isEncryptedBlob(blob: Buffer): boolean is the canonical guard.\nconst looksDecryptable = (b: Buffer): boolean => isEncryptedBlob(b);","tryCatchPattern":"try {\n  decryptBuffer(blob, key);\n} catch (e) {\n  const msg = e instanceof Error ? e.message : String(e);\n  if (msg.startsWith('decryptBuffer: blob too short')) {\n    // blob is corrupt/truncated; do not retry, surface to caller\n    throw new Error('secret blob is truncated; re-emit from source');\n  }\n  throw e;\n}","preventionTips":["Always call isEncryptedBlob() before decryptBuffer().","Verify file size against the expected minimum before reading.","Treat short blobs as corrupt, not as plaintext."],"tags":["encryption","validation","vault","buffer"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}