AI-generated analysisPublished automatically and not human-verified. Validated context appears in community notes below.
← Watch feed
Moderate 62 Monero

fix(CW-1638): include transaction notes in history CSV export (#3623)

Public commit record

What the developer wrote

Authored by claude[bot]

100/100 · Strong
fix(CW-1638): include transaction notes in history CSV export (#3623)

* fix(CW-1638): include transaction notes in history CSV export

The `note` column of the transaction-history CSV export was always written
as an empty string for transaction rows, so notes a user added to a
transaction never appeared in the exported file.

Index the `TransactionDescription` Hive box once per export and look each
transaction's note up by the same keys the app writes them under
(`<txHash>_<primaryAddress>`, falling back to the legacy bare `<txHash>`),
then write it through the existing `escapeField` so multi-line notes and
notes containing commas or quotes stay valid CSV.

`CsvExportService` now takes the box, so it is resolved through `getIt` at
both export entry points.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5EdTdWZSvzbKKmd9MCEJh

* perf(CW-1638): resolve the note-key address once per CSV export

`_noteFor` read `balanceViewModel.wallet.walletAddresses.primaryAddress`
once per transaction row. On Monero and Wownero that getter is not a field
read: it calls `getAddress`, which issues an FFI `numSubaddresses` call plus
four `ffiAddress` calls before its address cache is consulted. Since
`buildCsvContent` runs on the main isolate, exporting a wallet with a long
history meant thousands of synchronous FFI round-trips with no yield point.

Every item in one export belongs to the same wallet, so the value is
invariant across rows. Resolve it once in `buildCsvContent` and thread it
through to `_noteFor`. An export holding no transaction rows now skips the
read entirely.

No change to the exported output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5EdTdWZSvzbKKmd9MCEJh

* fix(CW-1638): neutralise spreadsheet formulas in CSV export

The note column this PR adds is fed by TransactionDescription.transactionNote,
which is not always authored by the user: a scanned bitcoin: URI carries
tx_description / message straight through payment_request.dart into
Output.note, and an address-resolution result writes Output.note too. Both are
folded into the note persisted by SendViewModel.

escapeField only quoted for , " and \n, so a note beginning with = + - @ TAB or
CR reached the cell untouched and Excel / LibreOffice / Sheets evaluate it as a
formula (=IMPORTDATA to exfiltrate a neighbouring cell, =HYPERLINK for phishing
anchor text, =cmd|… for DDE). trade.memo and the provider-supplied trade/order
strings had the same exposure already.

Neutralise inside escapeField rather than at the two call sites so every column
is covered and a future free-text column cannot forget it. Values starting with
a trigger are prefixed with an apostrophe, the spreadsheet text marker, guarded
by a plain-number test so negative amounts are left alone — formatFixed really
can emit a leading '-'. Also quote on a bare \r, which previously passed through
unquoted and splits rows in many CSV parsers.

Tests cover the escaping rules, the neutralisation, a row built end to end, and
the note column itself, including the two lookup keys and the payment-request
injection path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* perf(CW-1638): bound the CSV note lookup to the exported rows

Review feedback: the note index copied every entry in the shared
transactionDescriptionBox, including notes belonging to other wallets,
so its memory scaled with the box rather than with the export.

Collect the keys the exported rows can ask for first — for each
transaction row both `<txHash>_<primaryAddress>` and the bare
`<txHash>` — then walk the box once and keep only matching notes.
Lookups stay O(1) per row and first-wins/key-preference behaviour is
unchanged; a box walk is skipped entirely when the export has no
transaction rows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5EdTdWZSvzbKKmd9MCEJh

* refactor(CW-1638): simplify the CSV note lookup to a plain scan

Notes are written into the TransactionDescriptions box with `add`
(send_view_model.dart:1257,1263 and transaction_details_view_model.dart:299),
so entries carry auto-incrementing integer keys and `box.get(descriptionKey)`
cannot find them. Drop the prebuilt lookup map and the wanted-key set in
favour of a per-row scan that mirrors the app's own read path in
`TransactionDetailsViewModel.note`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5EdTdWZSvzbKKmd9MCEJh

* docs(CW-1638): drop the explanatory comments from the CSV note lookup [skip ci]

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5EdTdWZSvzbKKmd9MCEJh

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Omar <omarh.ismail1@gmail.com>
✓ Specific, descriptive subject✓ Names a concrete action or component✓ Uses a recognizable type or scope✓ Provides detailed explanatory context✓ Mentions testing or verification✓ Links an issue, advisory, or supporting reference
The short version

What changed, and why it matters

This commit fixes two related problems in Cake Wallet's CSV transaction-history export. First, user notes attached to transactions were being left blank in the exported file. Second, and more importantly, the CSV export did not protect against spreadsheet formula injection: a transaction note, trade memo, or other text that starts with characters like =, +, -, @, tab, or carriage return could be interpreted as a formula by Excel, LibreOffice, or Google Sheets when the CSV is opened. That could be abused to trick users into clicking malicious links, leak data from other cells, or run commands. The patch now prefixes such values with a single quote so spreadsheets treat them as plain text, and it also properly quotes carriage returns so rows don't get broken.

Recommended action

Treat this as a security-hardening fix and include it in the next release. Review other export formats (PDF, backup files, share sheets) for the same CSV/formula-injection risk. Ensure the new unit tests run in CI. Consider whether untrusted payment-request descriptions should be sanitized at the point of storage, not only at export time.

Security signals we found

01

CSV formula injection (DDE, HYPERLINK, IMPORTDATA) neutralized by prefixing trigger characters with apostrophe

02

Carriage-return characters now trigger RFC 4180 quoting to prevent row splitting

03

User-supplied transaction notes are now included in CSV export and sanitized

04

Trade memos and provider-supplied strings are also covered by the same sanitization

05

Payment-request-derived notes (bitcoin: URI tx_description/message) identified as an untrusted input path

Risk score

Why this scored 62/100

Our methodology →
Potential impact 18/30
Exploitability 12/25
Stealth signal 10/15
Affected reach 10/15
Confidence 8/10
Evidence quality 4/5
Human-validated context

Community notes

Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.

No validated notes yet.

The AI analysis stands alone for now. Submit a note if you can add evidence or important context.