From b1e6949fe4c9dc83cc3e9727b7ca93bc9d068e8e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 30 Jul 2026 07:32:54 +0000 Subject: [PATCH] Escape CSV cells that start with tab or carriage return Trim-based formula detection skipped the old rule for leading control characters; check those bytes before trim. Signed-off-by: Cursor Agent Co-authored-by: Bryan FRIMIN --- pkg/safecsv/cell.go | 18 ++++++++++++++++-- pkg/safecsv/cell_test.go | 2 ++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pkg/safecsv/cell.go b/pkg/safecsv/cell.go index 78828766c..0be60d0b7 100644 --- a/pkg/safecsv/cell.go +++ b/pkg/safecsv/cell.go @@ -41,13 +41,18 @@ func SanitizeRecord(record []string) []string { } // SanitizeCell prefixes values that spreadsheet tools may interpret as formulas. -// Leading Unicode whitespace (including newlines) is ignored for detection only; -// the written cell keeps the original text with a leading single-quote escape. +// Leading spaces are ignored for formula detection only; tab and carriage return +// at the start are always escaped. The written cell keeps the original text with +// a leading single-quote when sanitization applies. func SanitizeCell(value string) string { if value == "" { return value } + if spreadsheetLeadingControl(value[0]) { + return "'" + value + } + trimmed := strings.TrimLeftFunc(value, unicode.IsSpace) if trimmed == "" { return value @@ -61,6 +66,15 @@ func SanitizeCell(value string) string { return value } +func spreadsheetLeadingControl(b byte) bool { + switch b { + case '\t', '\r': + return true + default: + return false + } +} + func formulaLeadingRune(r rune) bool { switch r { case '=', '+', '-', '@', '\\', '|', '%': diff --git a/pkg/safecsv/cell_test.go b/pkg/safecsv/cell_test.go index 7d2a754be..466c44169 100644 --- a/pkg/safecsv/cell_test.go +++ b/pkg/safecsv/cell_test.go @@ -38,6 +38,8 @@ func TestSanitizeCell(t *testing.T) { assert.Equal(t, "'@sum", SanitizeCell("@sum")) assert.Equal(t, "' =1+1", SanitizeCell(" =1+1")) assert.Equal(t, "'\n=1+1", SanitizeCell("\n=1+1")) + assert.Equal(t, "'\tplain", SanitizeCell("\tplain")) + assert.Equal(t, "'\rhello", SanitizeCell("\rhello")) assert.Equal(t, "'\\evil", SanitizeCell("\\evil")) assert.Equal(t, "'-5", SanitizeCell("-5")) }