{"record":{"id":"836a1b90d1ebc8a6","repo":"danny-avila/LibreChat","slug":"decryption-failed-for-plugin-pluginkey-field","errorCode":null,"errorMessage":"Decryption failed for plugin ${pluginKey}, field ${auth.authField}: ${message}","messagePattern":"Decryption failed for plugin (.+?), field (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/api/src/agents/auth.ts","lineNumber":65,"sourceCode":"    /** Single loop through requested pluginKeys */\n    for (const pluginKey of pluginKeys) {\n      authMap[pluginKey] = {};\n      const auths = authsByPlugin.get(pluginKey) || [];\n\n      for (const auth of auths) {\n        decryptionPromises.push(\n          (async () => {\n            try {\n              const decryptedValue = await decrypt(auth.value);\n              authMap[pluginKey][auth.authField] = decryptedValue;\n            } catch (error) {\n              const message = error instanceof Error ? error.message : 'Unknown error';\n              logger.error(\n                `[getPluginAuthMap] Decryption failed for userId ${userId}, plugin ${pluginKey}, field ${auth.authField}: ${message}`,\n              );\n\n              if (throwError) {\n                throw new Error(\n                  `Decryption failed for plugin ${pluginKey}, field ${auth.authField}: ${message}`,\n                );\n              }\n            }\n          })(),\n        );\n      }\n    }\n\n    await Promise.all(decryptionPromises);\n    return authMap;\n  } catch (error) {\n    const message = error instanceof Error ? error.message : 'Unknown error';\n    const plugins = pluginKeys?.join(', ') ?? 'all requested';\n    logger.warn(\n      `[getPluginAuthMap] Failed to fetch auth values for userId ${userId}, plugins: ${plugins}: ${message}`,\n    );\n    if (!throwError) {","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/packages/api/src/agents/auth.ts#L47-L83","documentation":"getPluginAuthMap decrypts each stored plugin auth value with decrypt(). When decryption rejects and throwError is true (the default), it throws this error embedding the plugin key, auth field, and the underlying crypto message. With throwError false the failure is logged and that field is simply omitted from the returned map.","triggerScenarios":"Calling getPluginAuthMap with throwError=true (default) for a user whose stored IPluginAuth.value cannot be decrypted — typically because the encryption key changed, the ciphertext is corrupt, or the record was encrypted by a different key version.","commonSituations":"Rotating the app's encryption key without re-encrypting existing plugin auth values; restoring a database backup from a server with a different key; a botched migration that truncated the ciphertext; switching encryption algorithms without a re-encrypt pass.","solutions":["If the key rotation is the cause, re-encrypt affected plugin auth values with the new key (or keep the old key available for a decryption pass).","Call getPluginAuthMap with throwError=false where you want best-effort behavior so one bad field does not fail the whole map.","Audit the plugin auth collection for records whose ciphertext is malformed and have users re-enter credentials.","Verify the ENCRYPTION_KEY env var matches the one used when the values were stored."],"exampleFix":"// before\nconst map = await getPluginAuthMap({ userId, pluginKeys, findPluginAuthsByKeys }); // throws\n\n// after — best-effort, omit undecryptable fields\nconst map = await getPluginAuthMap({\n  userId,\n  pluginKeys,\n  throwError: false,\n  findPluginAuthsByKeys,\n});","handlingStrategy":"try-catch","validationCode":"// before decrypting, sanity-check ciphertext shape (version prefix, base64 length)\nfunction looksLikeCiphertext(v: unknown): boolean {\n  return typeof v === 'string' && v.length > 16 && /^[A-Za-z0-9+/=]+$/.test(v);\n}","typeGuard":null,"tryCatchPattern":"let map;\ntry {\n  map = await getPluginAuthMap({ userId, pluginKeys, throwError: true, findPluginAuthsByKeys });\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith('Decryption failed')) {\n    logger.warn('plugin auth decrypt failed; continuing with empty map', { error });\n    map = pluginKeys.reduce((acc, k) => { acc[k] = {}; return acc; }, {} as PluginAuthMap);\n  } else {\n    throw error;\n  }\n}","preventionTips":["Keep the encryption key stable, or run a re-encryption migration when rotating it.","Use throwError=false for best-effort reads so one bad field does not fail the whole map.","Audit plugin auth records for malformed ciphertext after restores/migrations.","Have users re-enter credentials when their stored value cannot be decrypted."],"tags":["plugins","auth","crypto","encryption","secrets"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}