{"record":{"id":"db841a5048c8a9d1","repo":"actualbudget/actual","slug":"zipmeta-getunsafeziperror-zipmeta-error","errorCode":null,"errorMessage":"zipMeta ? getUnsafeZipError(zipMeta) : error","messagePattern":"zipMeta \\? getUnsafeZipError\\(zipMeta\\) : error","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/budgetfiles/budgetfilesSlice.ts","lineNumber":250,"sourceCode":"        : new Error('Error duplicating budget: ' + String(error));\n    } finally {\n      dispatch(setAppState({ loadingText: null }));\n    }\n  },\n);\n\ntype ImportBudgetPayload = {\n  filepath: string;\n  type: Parameters<Handlers['import-budget']>[0]['type'];\n};\n\nexport const importBudget = createAppAsyncThunk(\n  `${sliceName}/importBudget`,\n  async ({ filepath, type }: ImportBudgetPayload, { dispatch }) => {\n    const { error, meta } = await send('import-budget', { filepath, type });\n    if (error) {\n      const zipMeta = getUnsafeZipMeta(meta);\n      throw new Error(zipMeta ? getUnsafeZipError(zipMeta) : error);\n    }\n\n    dispatch(closeModal());\n    await dispatch(loadPrefs());\n  },\n);\n\ntype UploadBudgetPayload = {\n  id?: string;\n};\n\nexport const uploadBudget = createAppAsyncThunk(\n  `${sliceName}/uploadBudget`,\n  async ({ id }: UploadBudgetPayload, { dispatch }) => {\n    const { error } = await send('upload-budget', { id });\n    if (error) {\n      return { error };\n    }","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/budgetfiles/budgetfilesSlice.ts#L232-L268","documentation":"`importBudget` sends the file path to the backend via `import-budget`; if the backend returns an `error`, the thunk throws it as a JS Error. Before throwing it checks `getUnsafeZipMeta(meta)` — when the failure is a flagged unsafe zip archive (e.g. zip-slip path traversal or unsafe compression ratio), the error is replaced with the more specific `getUnsafeZipError(zipMeta)` message. Otherwise the raw backend error string is thrown.","triggerScenarios":"Calling `dispatch(importBudget({ filepath, type }))` when the backend import fails: the file at `filepath` is not a valid/recognizable budget or export format, the file is a malicious/unsafe zip archive (detected via zip metadata in `meta`), the path is unreadable, or the imported data fails backend validation — any case where `send('import-budget')` resolves with a truthy `error`.","commonSituations":"Importing a corrupted or truncated .zip budget export; importing a random zip that isn't an Actual export; importing YNAB4/YNAB5/Actual files with schema problems; on desktop, a file path that no longer exists or lacks read permission.","solutions":["Read the thrown message: if it is the unsafe-zip error, re-export the budget from the source application and avoid untrusted/hand-edited zip files.","Verify the file path exists and is readable before importing; on desktop re-pick the file via the file dialog.","Confirm the file type matches the `type` argument (e.g. 'ynab4', 'ynab5', 'actual') — importing with the wrong type fails validation.","Try importing an uncompressed/known-good export to isolate whether the archive itself is the problem."],"exampleFix":"// before\nawait dispatch(importBudget({ filepath: '/downloads/budget.zip', type: 'actual' }));\n// after\nconst stat = await window.fs.stat(filepath); // or fs.existsSync in node context\nif (!stat) { alert('File not found: ' + filepath); return; }\ntry {\n  await dispatch(importBudget({ filepath, type: 'actual' })).unwrap();\n} catch (e) {\n  alert('Import failed: ' + (e instanceof Error ? e.message : String(e)));\n}","handlingStrategy":"try-catch","validationCode":"if (!filepath || !(await fileExists(filepath))) {\n  alert('Import file not found: ' + filepath);\n  return;\n}\nconst lower = filepath.toLowerCase();\nif (!['.zip', '.ynab', '.json', '.csv', '.ofx', '.qfx'].some(ext => lower.endsWith(ext))) {\n  alert('Unsupported import file type.');\n  return;\n}","typeGuard":"function isImportFailure(\n  e: unknown,\n): e is Error & { message: string } {\n  return e instanceof Error &&\n    (e.message.includes('zip') || e.message.includes('import'));\n}","tryCatchPattern":"try {\n  await dispatch(importBudget({ filepath, type })).unwrap();\n} catch (e) {\n  const msg = e instanceof Error ? e.message : String(e);\n  if (/zip/i.test(msg)) {\n    alert('The archive is unsafe or corrupted. Re-export the budget and try again.');\n  } else {\n    alert('Import failed: ' + msg);\n  }\n}","preventionTips":["Only import archives exported by a trusted application (Actual export, YNAB export).","Validate the file exists and the extension matches the declared `type` before dispatching.","Never hand-edit zip archives; re-export from source after data changes.","Keep the client updated so zip-slip/bomb detection rules match current security expectations."],"tags":["import","zip","file-validation","budget"],"backgroundTag":"unsafe-zip-archive","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}