ruvnet/ruflo · error · Error

decryptBuffer: blob too short (${blob.length}B; need >= ${MI

Error message

decryptBuffer: blob too short (${blob.length}B; need >= ${MIN_BLOB_LEN}B)

What it means

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.

Source

Thrown at v3/@claude-flow/cli/src/encryption/vault.ts:155

 * Decrypt a wire-format blob. Verifies the magic byte (sanity), parses
 * iv + ciphertext + tag, runs AES-256-GCM decrypt, and lets the GCM
 * auth tag fail loudly on tamper (Node throws "Unsupported state or
 * unable to authenticate data" — we let that propagate).
 *
 * Pre-condition: caller has already determined this is an encrypted
 * blob via isEncryptedBlob(). decryptBuffer throws on bad magic so a
 * mistaken plaintext blob still fails loudly rather than producing
 * garbage.
 */
export function decryptBuffer(blob: Buffer, key: Buffer): Buffer {
  if (!Buffer.isBuffer(blob)) {
    throw new TypeError('decryptBuffer: blob must be a Buffer');
  }
  if (!Buffer.isBuffer(key) || key.length !== KEY_LEN) {
    throw new TypeError(`decryptBuffer: key must be a ${KEY_LEN}-byte Buffer`);
  }
  if (blob.length < MIN_BLOB_LEN) {
    throw new Error(
      `decryptBuffer: blob too short (${blob.length}B; need >= ${MIN_BLOB_LEN}B)`,
    );
  }
  const magic = blob.subarray(0, MAGIC_LEN);
  // timingSafeEqual to avoid an oracle on the magic bytes specifically;
  // not strictly required (the magic isn't secret) but cheap and correct.
  if (!timingSafeEqual(magic, MAGIC)) {
    throw new Error(
      'decryptBuffer: bad magic — blob is not Ruflo-encrypted (RFE1)',
    );
  }
  const iv = blob.subarray(MAGIC_LEN, MAGIC_LEN + IV_LEN);
  const tag = blob.subarray(blob.length - TAG_LEN);
  const ciphertext = blob.subarray(MAGIC_LEN + IV_LEN, blob.length - TAG_LEN);

  const decipher = createDecipheriv(ALG, key, iv);
  decipher.setAuthTag(tag);
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]);

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Call isEncryptedBlob(blob) before decryptBuffer; if it returns false, treat the value as plaintext (or refuse).
  2. Verify the source file's size against the expected minimum before reading.
  3. Re-emit/re-download the blob if it was truncated in transit.

Example fix

// before
decryptBuffer(buf, key)   // buf was truncated
// after
import { isEncryptedBlob } from '../encryption/vault.js';
if (!isEncryptedBlob(buf)) {
  // not vault-encrypted — handle as plaintext or refuse
  throw new Error('blob is not vault-encrypted');
}
decryptBuffer(buf, key);
Defensive patterns

Strategy: type-guard

Validate before calling

import { isEncryptedBlob } from '../encryption/vault.js';
function safeDecrypt(buf: Buffer, key: Buffer): Buffer {
  if (!isEncryptedBlob(buf)) {
    throw new Error('blob is not vault-encrypted (too short or wrong magic)');
  }
  return decryptBuffer(buf, key);
}

Type guard

import { isEncryptedBlob } from '../encryption/vault.js';
// isEncryptedBlob(blob: Buffer): boolean is the canonical guard.
const looksDecryptable = (b: Buffer): boolean => isEncryptedBlob(b);

Try / catch

try {
  decryptBuffer(blob, key);
} catch (e) {
  const msg = e instanceof Error ? e.message : String(e);
  if (msg.startsWith('decryptBuffer: blob too short')) {
    // blob is corrupt/truncated; do not retry, surface to caller
    throw new Error('secret blob is truncated; re-emit from source');
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/0fd9591567b81c51. Report an issue: GitHub.