{"record":{"id":"a71afe1eea117df3","repo":"actualbudget/actual","slug":"decrypt-failure-a71afe","errorCode":"decrypt-failure","errorMessage":"decrypt-failure","messagePattern":"decrypt-failure","errorType":"error_code","errorClass":"FileDownloadError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/cloud-storage.ts","lineNumber":499,"sourceCode":"  if (userFileInfoRes.status !== 'ok') {\n    logger.log(\n      'Could not download file from the server. Are you sure you have the right file ID?',\n      userFileInfoRes,\n    );\n    throw FileDownloadError('internal', { fileId: cloudFileId });\n  }\n\n  const fileData = userFileInfoRes.data;\n  let buffer = userFileRes;\n\n  // The download process checks if the server gave us decrypt\n  // information. It is assumed that this key has already been loaded\n  // in, which is done in a previous step\n  if (fileData.encryptMeta) {\n    try {\n      buffer = await encryption.decrypt(buffer, fileData.encryptMeta);\n    } catch (e) {\n      throw FileDownloadError('decrypt-failure', {\n        isMissingKey: e.message === 'missing-key',\n      });\n    }\n  }\n\n  return importBuffer(fileData, buffer);\n}\n","sourceCodeStart":481,"sourceCodeEnd":507,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/cloud-storage.ts#L481-L507","documentation":"Thrown by the download handler in packages/loot-core/src/server/cloud-storage.ts when an encrypted budget file downloaded from cloud storage cannot be decrypted locally with the loaded encryption key. The app wraps the underlying encryption.decrypt() failure (including the 'missing-key' case) into a FileDownloadError with code 'decrypt-failure' and an isMissingKey flag. This prevents a corrupt or keyless decrypt attempt from silently producing garbage budget data.","triggerScenarios":"Calling download-budget (or the cloud download path in @actual-app/api) for a budget file whose fileData.encryptMeta is set, when the correct end-to-end encryption key is not loaded in memory or the supplied password/key is wrong; also when the stored ciphertext is corrupted.","commonSituations":"Downloading an end-to-end-encrypted budget on a fresh device/keyring where the key was never fetched; restoring data after clearing local storage; password changed on another device so local key is stale; server data corrupted or tampered.","solutions":["Obtain the correct password/key for the file and re-run key creation/key-test (handlers['key-test']) before downloading","Ensure the budget's key is loaded via the sync server key endpoints (download-budget with the password parameter)","If isMissingKey is true, prompt the user for the encryption password rather than retrying","Verify the cloud file integrity (re-download); if the ciphertext is corrupt, restore from another device's copy"],"exampleFix":"// before\nconst { data } = await api.downloadBudget(cloudFileId);\n// after\nconst { data, error } = await api.downloadBudget(cloudFileId, { password });\nif (error?.reason === 'decrypt-failure') {\n  throw new Error(error.isMissingKey ? 'Password required to decrypt budget' : 'Wrong decryption key/password');\n}","handlingStrategy":"try-catch","validationCode":"const files = await api.getBudgetFiles();\nconst file = files.find(f => f.cloudFileId === cloudFileId);\nif (file?.encryptMeta) console.warn('Encrypted budget: ensure key/password is available before download');","typeGuard":"function isFileDownloadError(e: unknown): e is { type: 'decrypt-failure'; isMissingKey?: boolean } {\n  return typeof e === 'object' && e !== null && (e as any).type === 'decrypt-failure';\n}","tryCatchPattern":"try {\n  await api.downloadBudget(cloudFileId);\n} catch (e) {\n  if (isFileDownloadError(e)) {\n    if (e.isMissingKey) promptForPassword();\n    else throw new Error('Cannot decrypt budget: wrong or corrupt key');\n  } else throw e;\n}","preventionTips":["Always keep the encryption key/password for E2E-encrypted budgets in a recoverable secret store","Run key-test before downloading to verify the password matches","Back up budgets before changing encryption keys"],"tags":["encryption","sync","budget-download"],"backgroundTag":"decrypt-failure","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}