ruvnet/ruflo · error · Error
Invalid ${ENV_KEY_VAR}: expected 32-byte key as 64-char hex
Error message
Invalid ${ENV_KEY_VAR}: expected 32-byte key as 64-char hex or 44-char base64 What it means
Thrown by decodeKey() when the supplied CLAUDE_FLOW_ENCRYPTION_KEY matches neither the strict 64-char hex regex nor the 43/44-char base64 regex yielding a 32-byte buffer. The vault would rather fail loudly than encrypt with a truncated key (which would silently produce ciphertext no correct key can decrypt).
Source
Thrown at v3/@claude-flow/cli/src/encryption/vault.ts:108
}
/**
* Decode a key string. Exposed for testing and for the future passphrase
* resolver, which will scrypt-derive a Buffer and hand it back through here
* to share the same length-check.
*/
export function decodeKey(raw: string): Buffer {
const trimmed = raw.trim();
// Hex first — strict 64 chars [0-9a-fA-F]
if (/^[0-9a-fA-F]{64}$/.test(trimmed)) {
return Buffer.from(trimmed, 'hex');
}
// Base64 — accept padded 44-char or unpadded 43-char forms
if (/^[A-Za-z0-9+/]{43}=?$/.test(trimmed)) {
const buf = Buffer.from(trimmed, 'base64');
if (buf.length === KEY_LEN) return buf;
}
throw new Error(
`Invalid ${ENV_KEY_VAR}: expected 32-byte key as 64-char hex or 44-char base64`,
);
}
/**
* Encrypt a plaintext Buffer with AES-256-GCM. Returns the wire-format
* blob: magic(4) || iv(12) || ciphertext(N) || tag(16).
*
* The IV is freshly randomized per call. Reusing a (key, iv) pair under
* GCM is catastrophic — every call MUST produce a different IV. Node's
* randomBytes is csprng-backed so this is automatic; the function takes
* no IV input deliberately.
*/
export function encryptBuffer(plaintext: Buffer, key: Buffer): Buffer {
if (!Buffer.isBuffer(plaintext)) {
throw new TypeError('encryptBuffer: plaintext must be a Buffer');
}
if (!Buffer.isBuffer(key) || key.length !== KEY_LEN) {View on GitHub (pinned to 6b01dc5a68)
Solutions
- Regenerate the key cleanly: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"` and copy the full 64 hex chars.
- Verify length and charset before deploying: hex must be exactly 64 chars of [0-9a-f].
- If using base64, ensure it is exactly 44 chars with padding (or 43 without) and decodes to 32 bytes.
- Do NOT pass a human passphrase — wait for the ADR-096 passphrase/keychain support.
Example fix
// before CLAUDE_FLOW_ENCRYPTION_KEY=my-password // wrong shape // after CLAUDE_FLOW_ENCRYPTION_KEY=9f3c...64 hex chars...a1 // 64-char hex of 32 random bytes
Defensive patterns
Strategy: validation
Validate before calling
function validateKeyShape(raw: string): void {
const t = raw.trim();
if (/^[0-9a-fA-F]{64}$/.test(t)) return;
if (/^[A-Za-z0-9+/]{43}=?$/.test(t) && Buffer.from(t, 'base64').length === 32) return;
throw new Error('CLAUDE_FLOW_ENCRYPTION_KEY must be 64-char hex or 44-char base64 (32 bytes)');
} Type guard
const isVaultKey = (v: unknown): v is string =>
typeof v === 'string' &&
(/^[0-9a-fA-F]{64}$/.test(v.trim()) ||
(/^[A-Za-z0-9+/]{43}=?$/.test(v.trim()) && Buffer.from(v.trim(), 'base64').length === 32)); Try / catch
try {
decodeKey(raw);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (msg.startsWith('Invalid CLAUDE_FLOW_ENCRYPTION_KEY')) {
console.error('Regenerate: node -e "console.log(crypto.randomBytes(32).toString(\'hex\'))"');
process.exit(2);
}
throw e;
} Prevention
- Generate keys only with crypto.randomBytes(32).toString('hex').
- Validate length/charset before deploying the env var.
- Never use a human passphrase until ADR-096 passphrase support ships.
When it happens
Trigger: Key with trailing whitespace/newline that survives into a non-64 length, a hex string with one character too few/many, a base64 string without padding, a raw 32-byte string passed as text, or a key copied with a missing character.
Common situations: Editor/copy adding a trailing newline that is not trimmed (note decodeKey does .trim(), so this is usually fine), shell variable expansion dropping a character, hand-pasting a partial hash, or using a passphrase string directly instead of a derived key.
Related errors
- ${ENV_ENABLE_FLAG} is set but ${ENV_KEY_VAR} is not. Provide
- decryptBuffer: blob too short (${blob.length}B; need >= ${MI
- decryptBuffer: bad magic — blob is not Ruflo-encrypted (RFE1
- Validation failed: ${result.error}
- Header JSON exceeds maximum size (${headerLen} > ${MAX_HEADE
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/743792c2ad92a102.
Report an issue: GitHub.