ruvnet/ruflo · error · Error
Encrypted buffer too short: need >= ${minLen}B, got ${buf.le
Error message
Encrypted buffer too short: need >= ${minLen}B, got ${buf.length}B What it means
Thrown by decryptApiKeys() when the supplied buffer is shorter than the minimum AES-256-GCM envelope (32B salt + 16B IV + 16B auth tag + at least 1B ciphertext = 65 bytes). The encryption format is produced only by the paired encryptApiKeys() in rvfa-builder.ts, so any buffer that does not originate from that function is invalid. This guard prevents feeding truncated or foreign data into scrypt/AES which would otherwise read garbage offsets.
Source
Thrown at v3/@claude-flow/cli/src/appliance/rvfa-builder.ts:82
/** Encrypt API keys from a .env file. Output: salt(32)+iv(16)+tag(16)+ciphertext */
export function encryptApiKeys(envPath: string, passphrase: string): Buffer {
const keys = parseEnvFile(readFileSync(envPath, 'utf-8'));
const plaintext = Buffer.from(JSON.stringify(keys), 'utf-8');
const salt = randomBytes(SCRYPT_SALT_LEN);
const key = scryptSync(passphrase, salt, SCRYPT_KEY_LEN, SCRYPT_OPTS);
const iv = randomBytes(AES_IV_LEN);
const cipher = createCipheriv(AES_ALG, key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
return Buffer.concat([salt, iv, cipher.getAuthTag(), encrypted]);
}
/** Decrypt API keys previously encrypted with encryptApiKeys. */
export function decryptApiKeys(buf: Buffer, passphrase: string): Record<string, string> {
const minLen = SCRYPT_SALT_LEN + AES_IV_LEN + AES_TAG_LEN + 1;
if (buf.length < minLen) {
throw new Error(`Encrypted buffer too short: need >= ${minLen}B, got ${buf.length}B`);
}
let off = 0;
const salt = buf.subarray(off, off += SCRYPT_SALT_LEN);
const iv = buf.subarray(off, off += AES_IV_LEN);
const tag = buf.subarray(off, off += AES_TAG_LEN);
const ciphertext = buf.subarray(off);
const key = scryptSync(passphrase, salt, SCRYPT_KEY_LEN, SCRYPT_OPTS);
const decipher = createDecipheriv(AES_ALG, key, iv);
decipher.setAuthTag(tag);
return JSON.parse(
Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf-8'),
);
}
// ── Builder ──────────────────────────────────────────────────View on GitHub (pinned to 6b01dc5a68)
Solutions
- Ensure the buffer came from encryptApiKeys() and round-trips as Buffer (not a string).
- If reading from the RVFA vault field, base64-decode first: Buffer.from(vault.encrypted, 'base64').
- Confirm the buffer length is at least 65 before calling decryptApiKeys.
- Re-derive the buffer from a known-good encrypted source if it was corrupted in transit.
Example fix
// before const keys = decryptApiKeys(vault.encrypted, passphrase); // encrypted is a base64 string // after const keys = decryptApiKeys(Buffer.from(vault.encrypted, 'base64'), passphrase);
Defensive patterns
Strategy: validation
Validate before calling
const MIN_ENC_LEN = 32 + 16 + 16 + 1; // salt + iv + tag + >=1 byte ciphertext
function isValidEncryptedVault(buf: Buffer): boolean {
return Buffer.isBuffer(buf) && buf.length >= MIN_ENC_LEN;
} Type guard
function isEncryptedApiKeysBuffer(buf: unknown): buf is Buffer {
return Buffer.isBuffer(buf) && buf.length >= 65;
} Try / catch
try {
const keys = decryptApiKeys(buf, passphrase);
} catch (e) {
if (/Encrypted buffer too short/.test((e as Error).message)) {
throw new Error('Vault buffer is not a valid encryptApiKeys() output; expected base64-decoded Buffer >= 65 bytes');
}
throw e;
} Prevention
- Always round-trip the vault as a Buffer: store with enc.toString('base64'), restore with Buffer.from(b64, 'base64').
- Never pass a raw string, JSON object, or the plaintext .env to decryptApiKeys.
- Add a unit test that encrypts then decrypts to catch format regressions.
- Log buf.length before decrypting in debug builds to catch truncation early.
When it happens
Trigger: Calling decryptApiKeys(buf, passphrase) with a buffer under 65 bytes. Most commonly: passing the raw base64 string instead of Buffer.from(b64, 'base64'); passing an empty buffer; passing a JSON object that was not the encrypted output; truncating the vault field read from an RVFA section.
Common situations: The vault is stored in the appliance as enc.toString('base64') — forgetting to base64-decode it back to a Buffer before decrypting is the most frequent mistake. Other cases: reading the wrong .env file, a corrupted appliance image, or passing the plaintext keys buffer by accident.
Related errors
- Buffer too small for RVFP preamble
- Buffer too small to contain RVFA preamble
- decryptBuffer: blob too short (${blob.length}B; need >= ${MI
- decryptBuffer: bad magic — blob is not Ruflo-encrypted (RFE1
- unknown game "${key}". Known: ${Object.keys(GAMES).join(', '
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/fc0273a61c715aaa.
Report an issue: GitHub.