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
- 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.
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
- Always call isEncryptedBlob() before decryptBuffer().
- Verify file size against the expected minimum before reading.
- Treat short blobs as corrupt, not as plaintext.
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
- decryptBuffer: bad magic — blob is not Ruflo-encrypted (RFE1
- Invalid ${ENV_KEY_VAR}: expected 32-byte key as 64-char hex
- Encrypted buffer too short: need >= ${minLen}B, got ${buf.le
- Buffer too small for RVFP preamble
- Buffer too small to contain RVFA preamble
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/0fd9591567b81c51.
Report an issue: GitHub.