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

  1. Ensure the buffer came from encryptApiKeys() and round-trips as Buffer (not a string).
  2. If reading from the RVFA vault field, base64-decode first: Buffer.from(vault.encrypted, 'base64').
  3. Confirm the buffer length is at least 65 before calling decryptApiKeys.
  4. 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

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


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