From ae36ca3127ad1220773e2d1c93c745bb919ac941 Mon Sep 17 00:00:00 2001 From: Dhananjay Maurya Date: Tue, 13 Oct 2026 00:06:15 +0530 Subject: [PATCH] Keep plain negative numbers numeric in CSV export A leading '-' only matters when the value is not a plain number, so -12 and -1.5 stay numeric while payloads like -2+3+cmd|'/c calc'!A0 still get the apostrophe prefix. --- server/routers/auditLogs/generateCSV.test.ts | 12 ++++++++++++ server/routers/auditLogs/generateCSV.ts | 11 ++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/server/routers/auditLogs/generateCSV.test.ts b/server/routers/auditLogs/generateCSV.test.ts index 59229c033..15855f135 100644 --- a/server/routers/auditLogs/generateCSV.test.ts +++ b/server/routers/auditLogs/generateCSV.test.ts @@ -147,6 +147,18 @@ function runTests() { ); } + // A plain negative number is not a formula, so it must stay numeric + // instead of being forced to text with a leading apostrophe. + { + const csv = generateCSV([{ id: -12, path: "-1.5" }]); + + assertEquals( + csv, + "id,path\n-12,-1.5", + "Negative numbers should stay numeric" + ); + } + // A formula prefix is only dangerous at the start of a cell, so it must not // be added mid-value. { diff --git a/server/routers/auditLogs/generateCSV.ts b/server/routers/auditLogs/generateCSV.ts index ca9741377..762d0b6af 100644 --- a/server/routers/auditLogs/generateCSV.ts +++ b/server/routers/auditLogs/generateCSV.ts @@ -4,6 +4,12 @@ // leading apostrophe is prefixed to force the cell to be read as literal text. const FORMULA_PREFIX = /^[=+\-@\t\r]/; +// A leading "-" alone is not dangerous when the rest of the value is just a +// number: spreadsheets read -12 as a number, not a formula. Only values like +// "-2+3+cmd|'/c calc'!A0" (a DDE payload) actually need neutralising, so +// plain negative numbers stay numeric instead of being forced to text. +const NEGATIVE_NUMBER = /^-\d+(\.\d+)?$/; + // CRLF, CR and LF each terminate a record for at least one CSV parser, so a // value containing them would otherwise spill across rows and forge new ones. // Replacing them with a literal "\n" keeps every record on a single line. @@ -24,7 +30,10 @@ function toCsvField(value: unknown): string { // Quoting does not stop a spreadsheet from evaluating the unquoted value, // so the apostrophe has to be added before the field is escaped. - const cell = FORMULA_PREFIX.test(text) ? `'${text}` : text; + const cell = + FORMULA_PREFIX.test(text) && !NEGATIVE_NUMBER.test(text) + ? `'${text}` + : text; if (cell.includes(",") || cell.includes('"')) { return `"${cell.replace(/"/g, '""')}"`;