fix(audit): H10 — neutralize CSV formula injection in expense + audit exports
Adds sanitizeCsvCell() (prefixes a quote when a cell starts with = + - @ tab/CR) and applies it to the audit-export escape() and the user-controlled free-text columns of the expense export before Papa.unparse. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { withAuth, withPermission } from '@/lib/api/helpers';
|
||||
import { sanitizeCsvCell } from '@/lib/csv/sanitize-csv-cell';
|
||||
import { errorResponse } from '@/lib/errors';
|
||||
import { searchAuditLogs } from '@/lib/services/audit-search.service';
|
||||
|
||||
@@ -94,7 +95,10 @@ function buildCsv(rows: Awaited<ReturnType<typeof searchAuditLogs>>['rows']): st
|
||||
|
||||
const escape = (v: unknown): string => {
|
||||
if (v === null || v === undefined) return '';
|
||||
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
||||
const raw = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
||||
// Neutralize spreadsheet formula triggers before RFC 4180 framing —
|
||||
// the leading quote is part of the cell value, not the CSV escaping.
|
||||
const s = sanitizeCsvCell(raw);
|
||||
if (/[",\n\r]/.test(s)) {
|
||||
return `"${s.replace(/"/g, '""')}"`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user