- Location
- src/export/xlsx.exporter.ts:216
- Finding
- CSV Exports Permit Spreadsheet Formula Injection<![CDATA[
## Vulnerability Details
**File Location**: `src/export/xlsx.exporter.ts:216-240, 277-318, 351-370`
**Vulnerability Type**: CSV/Spreadsheet Formula Injection
**Risk Level**: High
### Vulnerable Code
```ts
// src/export/xlsx.exporter.ts:216-240
const rows = leads.map((lead) => {
const exportLead = leadToExportRow(lead);
return headers
.map((header) => {
const value = exportLead[header as keyof ExportLead];
// Escape quotes and wrap in quotes if contains comma
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
return `"${value.replace(/"/g, '""')}"`;
}
return value;
})
.join(',');
});
const csv = [headers.join(','), ...rows].join('\n');
```
```ts
// src/export/xlsx.exporter.ts:277-318
const instantlyRows = leads.map((lead) => {
const nameParts = splitName(lead.contactName);
return [
lead.email ?? '',
nameParts.firstName,
nameParts.lastName,
lead.companyName,
formatPhoneDisplay(lead.phone),
lead.website ?? '',
lead.city ?? '',
lead.state ?? '',
lead.trade,
lead.source,
lead.rating?.toString() ?? '',
]
.map((value) => {
// Escape quotes and wrap if contains comma/quote/newline
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
return `"${value.replace(/"/g, '""')}"`;
}
return value;
})
.join(',');
});
```
```ts
// src/export/xlsx.exporter.ts:351-370
function csvEscape(value: string): string {
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
return `"${value.replace(/"/g, '""')}"`;
}
return value;
}
async function writeCsvFile(path: string, headers: string[], rows: string[][]): Promise<void> {
const dir = dirname(path);
if (!existsSync(dir)) {
await mkdir(dir, { recursive: true });
}
const csv = [
headers.join(','),
...rows.map(row => row.map(csvEscape).join(',')),
].join('\n');
const { writeFile } = a
...[truncated 2154 chars]
- Remediation
- <![CDATA[
## Remediation Suggestions
1. Implement one centralized spreadsheet-cell sanitizer and use it for every CSV and XLSX field.
2. Before CSV quoting, detect values whose first effective character is `=`, `+`, `-`, or `@`.
3. Also detect leading tabs, carriage returns, newlines, Unicode whitespace, and other characters that spreadsheet applications may ignore before formula evaluation.
4. Neutralize dangerous cells using an application-compatible strategy, such as prefixing an apostrophe and documenting the resulting display behavior.
5. Do not rely on enclosing the value in double quotes; quoting is CSV syntax, not formula neutralization.
6. Apply equivalent protections to values written through ExcelJS, explicitly forcing untrusted content to be plain text where necessary.
7. Preserve separate raw and export-safe representations if exact original values must remain available.
8. Add regression tests for standard formulas, DDE-style payloads, hyperlink and web-request formulas, leading whitespace, tab-prefixed payloads, and all export formats.
9. Warn users that existing exports created before the fix should be treated as untrusted.
]]>