diff --git a/apps/sim/app/api/logs/export/route.ts b/apps/sim/app/api/logs/export/route.ts index 766435eadd4..f85816e26a1 100644 --- a/apps/sim/app/api/logs/export/route.ts +++ b/apps/sim/app/api/logs/export/route.ts @@ -5,6 +5,7 @@ import { and, desc, eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { MATERIALIZE_CONCURRENCY, mapWithConcurrency } from '@/lib/core/utils/concurrency' +import { neutralizeCsvFormula } from '@/lib/core/utils/csv' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { materializeExecutionData } from '@/lib/logs/execution/trace-store' import { buildFilterConditions, LogFilterParamsSchema } from '@/lib/logs/filters' @@ -16,7 +17,7 @@ export const revalidate = 0 function escapeCsv(value: any): string { if (value === null || value === undefined) return '' - const str = String(value) + const str = typeof value === 'string' ? neutralizeCsvFormula(value) : String(value) if (/[",\n]/.test(str)) { return `"${str.replace(/"/g, '""')}"` } diff --git a/apps/sim/app/api/table/[tableId]/export/route.ts b/apps/sim/app/api/table/[tableId]/export/route.ts index b1fbef15aec..096f9d6c5af 100644 --- a/apps/sim/app/api/table/[tableId]/export/route.ts +++ b/apps/sim/app/api/table/[tableId]/export/route.ts @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { tableExportFormatSchema, tableIdParamsSchema } from '@/lib/api/contracts/tables' import { getValidationErrorMessage } from '@/lib/api/server' import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid' +import { neutralizeCsvFormula } from '@/lib/core/utils/csv' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { buildNameById, getColumnId, rowDataIdToName } from '@/lib/table/column-keys' @@ -119,14 +120,6 @@ function sanitizeFilename(name: string): string { return cleaned || 'table' } -/** - * Prefixes a single quote to values starting with a spreadsheet formula trigger - * (`=`, `+`, `-`, `@`, tab, CR), neutralizing CSV injection in Excel/Sheets. - */ -function neutralizeCsvFormula(value: string): string { - return /^[=+\-@\t\r]/.test(value) ? `'${value}` : value -} - /** * Serializes a cell for CSV. Only string cells are formula-neutralized; numbers, * booleans, dates, and JSON objects can never form a trigger and pass through verbatim. diff --git a/apps/sim/lib/core/utils/csv.ts b/apps/sim/lib/core/utils/csv.ts new file mode 100644 index 00000000000..c02bf5a2314 --- /dev/null +++ b/apps/sim/lib/core/utils/csv.ts @@ -0,0 +1,7 @@ +/** + * Prefixes a single quote to values starting with a spreadsheet formula trigger + * (`=`, `+`, `-`, `@`, tab, CR), neutralizing CSV injection in Excel/Sheets. + */ +export function neutralizeCsvFormula(value: string): string { + return /^[=+\-@\t\r]/.test(value) ? `'${value}` : value +}