{"record":{"id":"743792c2ad92a102","repo":"ruvnet/ruflo","slug":"invalid-env-key-var-expected-32-byte-key-as-64","errorCode":null,"errorMessage":"Invalid ${ENV_KEY_VAR}: expected 32-byte key as 64-char hex or 44-char base64","messagePattern":"Invalid (.+?): expected 32-byte key as 64-char hex or 44-char base64","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/encryption/vault.ts","lineNumber":108,"sourceCode":"}\n\n/**\n * Decode a key string. Exposed for testing and for the future passphrase\n * resolver, which will scrypt-derive a Buffer and hand it back through here\n * to share the same length-check.\n */\nexport function decodeKey(raw: string): Buffer {\n  const trimmed = raw.trim();\n  // Hex first — strict 64 chars [0-9a-fA-F]\n  if (/^[0-9a-fA-F]{64}$/.test(trimmed)) {\n    return Buffer.from(trimmed, 'hex');\n  }\n  // Base64 — accept padded 44-char or unpadded 43-char forms\n  if (/^[A-Za-z0-9+/]{43}=?$/.test(trimmed)) {\n    const buf = Buffer.from(trimmed, 'base64');\n    if (buf.length === KEY_LEN) return buf;\n  }\n  throw new Error(\n    `Invalid ${ENV_KEY_VAR}: expected 32-byte key as 64-char hex or 44-char base64`,\n  );\n}\n\n/**\n * Encrypt a plaintext Buffer with AES-256-GCM. Returns the wire-format\n * blob: magic(4) || iv(12) || ciphertext(N) || tag(16).\n *\n * The IV is freshly randomized per call. Reusing a (key, iv) pair under\n * GCM is catastrophic — every call MUST produce a different IV. Node's\n * randomBytes is csprng-backed so this is automatic; the function takes\n * no IV input deliberately.\n */\nexport function encryptBuffer(plaintext: Buffer, key: Buffer): Buffer {\n  if (!Buffer.isBuffer(plaintext)) {\n    throw new TypeError('encryptBuffer: plaintext must be a Buffer');\n  }\n  if (!Buffer.isBuffer(key) || key.length !== KEY_LEN) {","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/encryption/vault.ts#L90-L126","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nCLAUDE_FLOW_ENCRYPTION_KEY=my-password   // wrong shape\n// after\nCLAUDE_FLOW_ENCRYPTION_KEY=9f3c...64 hex chars...a1   // 64-char hex of 32 random bytes","handlingStrategy":"validation","validationCode":"function validateKeyShape(raw: string): void {\n  const t = raw.trim();\n  if (/^[0-9a-fA-F]{64}$/.test(t)) return;\n  if (/^[A-Za-z0-9+/]{43}=?$/.test(t) && Buffer.from(t, 'base64').length === 32) return;\n  throw new Error('CLAUDE_FLOW_ENCRYPTION_KEY must be 64-char hex or 44-char base64 (32 bytes)');\n}","typeGuard":"const isVaultKey = (v: unknown): v is string =>\n  typeof v === 'string' &&\n  (/^[0-9a-fA-F]{64}$/.test(v.trim()) ||\n   (/^[A-Za-z0-9+/]{43}=?$/.test(v.trim()) && Buffer.from(v.trim(), 'base64').length === 32));","tryCatchPattern":"try {\n  decodeKey(raw);\n} catch (e) {\n  const msg = e instanceof Error ? e.message : String(e);\n  if (msg.startsWith('Invalid CLAUDE_FLOW_ENCRYPTION_KEY')) {\n    console.error('Regenerate: node -e \"console.log(crypto.randomBytes(32).toString(\\'hex\\'))\"');\n    process.exit(2);\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["encryption","validation","security","vault","env"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}