ruvnet/ruflo · error · Error
decryptBuffer: bad magic — blob is not Ruflo-encrypted (RFE1
Error message
decryptBuffer: bad magic — blob is not Ruflo-encrypted (RFE1)
What it means
Thrown by decryptBuffer() when the first 4 bytes of the blob do not equal the MAGIC bytes 'RFE1' (compared with timingSafeEqual). The blob is long enough to have a header but is not a Ruflo-encrypted blob — typically a plaintext value or ciphertext from a different/older encryption scheme. The guard exists so a mistaken plaintext input fails loudly rather than yielding garbage plaintext.
Source
Thrown at v3/@claude-flow/cli/src/encryption/vault.ts:163
* 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()]);
}
/**
* Magic-byte sniff. True iff the blob starts with the RFE1 magic AND is
* long enough to be a valid encrypted blob. Used by readers during the
* incremental migration: legacy plaintext files return false and flow
* through the existing read path unchanged.
*View on GitHub (pinned to 6b01dc5a68)
Solutions
- Gate decryption with isEncryptedBlob() and skip plaintext values (or migrate them) rather than unconditionally decrypting.
- If migrating an old format, write a one-time converter that re-encrypts under the current scheme.
- Confirm the file actually came from encryptBuffer() (the only writer of the RFE1 magic).
Example fix
// before
decryptBuffer(maybeCiphertext, key) // bad magic
// after
import { isEncryptedBlob } from '../encryption/vault.js';
const out = isEncryptedBlob(maybeCiphertext)
? decryptBuffer(maybeCiphertext, key)
: maybeCiphertext; // already plaintext Defensive patterns
Strategy: type-guard
Validate before calling
import { isEncryptedBlob } from '../encryption/vault.js';
function readSecret(buf: Buffer, key: Buffer): Buffer {
return isEncryptedBlob(buf) ? decryptBuffer(buf, key) : buf; // plaintext passthrough
} Type guard
import { isEncryptedBlob } from '../encryption/vault.js';
const isRufloCiphertext = (b: Buffer): boolean => isEncryptedBlob(b); Try / catch
try {
decryptBuffer(blob, key);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (msg.includes('bad magic')) {
// probably plaintext from a legacy store; handle migration
return blob;
}
throw e;
} Prevention
- Gate decryption with isEncryptedBlob(); pass through plaintext.
- Migrate legacy secrets through encryptBuffer() once.
- Confirm the blob came from the vault's encryptBuffer (the only RFE1 writer).
When it happens
Trigger: Passing a plaintext secret (never encrypted) to decryptBuffer, passing ciphertext produced by a different tool or an older format without the RFE1 magic, or a buffer that begins mid-stream.
Common situations: Migrating from a previous non-vault secret store and forgetting to encrypt first, a field that is sometimes plaintext and sometimes encrypted, or a down-level vault format predating the magic header.
Related errors
- decryptBuffer: blob too short (${blob.length}B; need >= ${MI
- 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/56ed9a7dfbaa21ff.
Report an issue: GitHub.