CSV injection, also called formula injection, happens when an export contains untrusted text that a spreadsheet program treats as a formula. Cells beginning with characters such as equals, plus, minus or at can be run when someone opens the file. Prevent it by sanitizing fields you export, knowing that no single fix suits every spreadsheet.
Where analytics teams meet it
Dashboards and reports offer "download as CSV". The rows often contain text typed by customers, survey respondents or support users. Someone then opens the file in a spreadsheet, usually an analyst or an executive, and that person's machine is the target.
OWASP describes the issue as a website putting untrusted input into CSV files, which spreadsheet programs may then run as formulas. It lists three outcomes: compromise of the user's computer through spreadsheet software vulnerabilities, exploiting users who ignore warnings on files from a trusted site, and extracting data from the open spreadsheet or others.
Characters to watch
According to OWASP, the dangerous leading characters are equals, plus, minus and at, plus tab, carriage return and line feed. Full width variants of the first four can act as formulas in some locales, for example in Japanese environments.
Checking only the first character of a field is not enough. Separators such as comma or semicolon, and quotes, matter, because an attacker can start a new cell and place a dangerous character at its start.
Mitigations from OWASP
| Approach | Notes |
|---|---|
| Make sure no cell starts with a listed character | Strictest, but changes legitimate values such as negative numbers |
| Wrap each field in double quotes, prepend a single quote, and escape inner quotes | Not reliable in Excel after the file is saved and re-opened, since Excel may remove quotes or escape characters |
| For Excel, prefix cells starting with equals, plus, minus or at with a tab inside the quoted field | The tab stays in the data, so this suits files meant for human viewing |
OWASP says the attack is hard to mitigate and that quite a few bug bounty programs disallow reports about it. It concludes there is no universal sanitization strategy for every spreadsheet application and downstream consumer.
A practical policy
- Separate exports for people from exports for machines. People get the sanitized version. Machines get JSON or Parquet.
- Apply sanitizing in one shared export function, not per report.
- Only sanitize text fields. Keep numeric columns numeric, so totals still work.
- Tell recipients that a file came from a system and may hold user text, and not to enable external content or macros.
- Test with a harmless marker value, such as a cell beginning with an equals sign and a simple sum, and confirm it displays as text.
def safe_cell(value):
text = str(value)
if text[:1] in ("=", "+", "-", "@", "\t", "\r", "\n"):
return "\t" + text
return text
This follows the tab prefix idea above, so apply it only to files meant for human viewing and review it against the spreadsheet your recipients use.
Where Safeguard fits
Safeguard's code scanning can help review export code paths, and you can begin with a free scan.
Quick check
Is a leading minus sign always an attack?
No. Negative numbers start with one. That is why sanitizing text fields only, and keeping numeric columns numeric, matters.
Does Parquet or JSON avoid this problem?
Those formats are not opened as spreadsheets by default, so the formula risk applies mainly when a spreadsheet program opens the data.
Sources
Last reviewed: 19 October 2026.