Skip to content

fix(reporting): escape +, -, tab, CR and LF prefixes in the CSV export - #8038

Open
yhabib wants to merge 4 commits into
mainfrom
fix/csv-formula-injection-escaping
Open

fix(reporting): escape +, -, tab, CR and LF prefixes in the CSV export#8038
yhabib wants to merge 4 commits into
mainfrom
fix/csv-formula-injection-escaping

Conversation

@yhabib

@yhabib yhabib commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Motivation

CSV export only escaped =, @ and | as formula-injection prefixes. Excel and LibreOffice also treat a cell that starts with +, -, a tab, a carriage return or a line feed as a formula. A token name or symbol can carry any of these and reach the export unescaped.

Changes

  • Added a signed-number exemption so amount cells such as +1.00 and -1'234.5678 stay unprefixed.
  • Extended the special-character check and the formula-prefix list to cover +, -, tab, carriage return and line feed.
  • Added unit tests for the new prefix cases and the signed-number exemption.
  • Added an e2e spec that exports a CSV from an account named with a hostile payload and checks the escaping.
  • Updated the changelog.

Tests

  • npm run check and npm run test pass.
  • ./scripts/check-relative-imports passes.
  • New and existing tests in reporting.utils.spec.ts and ReportingTransactionsButton.spec.ts cover the escaping and the amount-column exemption.

Todos

  • Accessibility (a11y) – no impact, the change alters CSV export bytes only.
  • Changelog – added under Security.

escapeCsvValue prefixed a single quote only when a cell started with '=',
'@' or '|'. Excel and LibreOffice also run a cell that starts with '+',
'-' or a tab. A token name or symbol comes from ledger metadata and
reaches the CSV without a filter.

Prefix those three characters too. Exempt a plain signed number, so the
amount column still shows "+1.00" and "-1'234.5678".
The spec creates a linked account whose name is a spreadsheet formula, then
exports the transactions CSV. It checks that the export prefixes the name with
a single quote, and that the amount column keeps its sign and no quote.
…CSV export

A spreadsheet reads a cell that starts with a carriage return or a line
feed as a formula, the same way it reads a leading tab. Those two
characters were missing from formulaInjectionCharacters, so such a cell
was quoted but never prefixed.

Add them to the list. Cover the two cases in the unit test and in the
FORMULA_CHARACTERS list of the e2e spec.
@yhabib
yhabib requested a review from a team as a code owner September 4, 2026 12:53
@yhabib
yhabib requested a lite review from Copilot September 4, 2026 12:53
@zeropath-ai

zeropath-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 085856f.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► frontend/src/lib/utils/reporting.utils.ts
    - Update CSV escaping logic to treat signed numbers as non-formula and broaden pattern for special characters
► frontend/src/tests/lib/utils/reporting.utils.spec.ts
    - Update tests to cover signed numbers and additional formula-like patterns in CSV escaping
► frontend/src/tests/e2e/reporting-csv-escaping.spec.ts
    - Add end-to-end test for CSV escaping of formula characters and signed numbers
► CHANGELOG-Nns-Dapp-unreleased.md
    - Update security note about CSV escaping (implied by diff)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new e2e test reads from download.path() without guarding against a possible null value, which can cause runtime failures and test flakiness in some Playwright configurations.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR strengthens CSV export hardening against spreadsheet formula injection by escaping additional leading characters (+, -, tab, CR, LF) while preserving legitimate signed numeric amounts (e.g., +20.00, -1'234.5678) and maintaining the existing Excel bigint wrapper behavior.

Changes:

  • Expanded CSV formula-injection prefix handling to include +, -, tab, carriage return, and line feed, with a signed-number exemption for amount-like values.
  • Added/updated unit tests for new prefix cases, signed-number exemption, and quoting interactions.
  • Added a Playwright e2e test validating end-to-end CSV escaping through the UI and updated the security changelog entry.
File summaries
File Description
frontend/src/lib/utils/reporting.utils.ts Extends CSV escaping logic for additional formula prefixes and exempts signed numeric amounts.
frontend/src/tests/lib/utils/reporting.utils.spec.ts Adds unit coverage for new escaping behavior and signed-number exemption.
frontend/src/tests/e2e/reporting-csv-escaping.spec.ts Adds an e2e spec that exports CSV and validates escaping in produced cells.
CHANGELOG-Nns-Dapp-unreleased.md Documents the security-relevant CSV export change.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread frontend/src/tests/e2e/reporting-csv-escaping.spec.ts
Copilot flagged that download.path() can return null and would throw
inside readFileSync. Throw a clear error instead so the failure is
easy to diagnose.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approved

The escaping logic change is targeted and is backed by both unit and e2e coverage that exercises the newly added prefix cases and the signed-number exemption.

Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants