Todd Mercer

CSV Formula Injection in AI Exports: Why Quoting Is Not Enough
Quoting a CSV field protects its syntax. It does not tell a spreadsheet application to treat the field as literal text. An AI-generated research report, support summary or lead list can carry formula-like content from an untrusted source into a human reviewer's spreadsheet.
An XLSX extension is not enough either. In our September 30, 2026 test, ordinary openpyxl assignment converted two harmless =1+1 strings into formula cells. Assigning explicit string types preserved all 13 test cells as literal strings, with no formula XML nodes. This was a file-format test, not an Excel security certification.
The practical decision is to separate three requirements: a parseable export, unchanged values, and non-executable cell types. Then validate the actual spreadsheet application that will receive the file.
What We Tested
We ran one synthetic fixture through four export paths using Python 3.13.14 and openpyxl 3.1.5 in Aident Loadout's Code Interpreter. The fixture contained 12 harmless values and one untrusted header. We did not open a spreadsheet application or execute a formula.
The header was =1+1. The values included leading equals, plus, minus and at-sign characters; tab, carriage return and line feed prefixes; a fullwidth equals character; ordinary text; a quoted comma; an embedded newline; and #N/A. Including the header matters: a user-controlled column label is still untrusted input.
For CSV, we parsed the output with Python's CSV reader and compared values. For XLSX, we reopened the workbook with data_only=False, checked values and cell types, and inspected its ZIP package for formula nodes, external relationship targets, macros and external-link parts.
Export path | Exact values after parsing or reopening | Formula cells | What the test establishes |
|---|---|---|---|
CSV with every field quoted | 13/13 | Not measured in a spreadsheet | CSV syntax preserves the formula-like input unchanged |
CSV with an apostrophe added to every field | 0/13 | Not measured in a spreadsheet | The parser retains the added prefix; underlying values change |
XLSX with ordinary cell assignment | 13/13 | 2 | Exact values can coexist with inferred formulas and one error cell |
XLSX with explicit string cell types | 13/13 | 0 | All 13 cells remain strings in this workbook and library round trip |
All 14 acceptance checks passed, including rejection of a 32,768-character input and a NUL character. Both generated workbooks passed ZIP CRC checks. Neither had external relationship targets, macro parts or external-link parts.
These are deterministic controls for one small fixture. They are not attack-prevalence measurements, application compatibility results or a ranking of spreadsheet products.
Why CSV Quotes Do Not Solve Formula Injection
CSV quoting answers questions such as: does a comma belong inside this field, and does a newline belong inside this value? After a CSV reader removes the syntactic quotes, "=1+1" is still the string =1+1.
Whether that string becomes a formula is a separate decision made by the receiving spreadsheet application. OWASP's CSV Injection guidance describes this boundary, lists relevant leading characters, and cautions that there is no universal sanitization strategy for every spreadsheet application and downstream consumer. Its guidance also addresses changes after saving and reopening files.
Our quoted-CSV control proves the first part: serialization and parsing left the leading equals sign intact. It does not prove what a particular Excel, LibreOffice or Google Sheets version would do. Those applications were outside the experiment.
This is relevant to AI exports because model output can repeat external text. Asking a model not to produce formulas is not an acceptance test for the resulting file. The exporter still needs an explicit policy for untrusted strings, including headings and labels.
A recent concrete example is ihub-apps' July 8 export fix, which addresses formula-like AI text in CSV and XLSX outputs. That source shows the export boundary being maintained in a real application; it is not evidence that every AI exporter is vulnerable or that one mitigation works everywhere.
Why an Apostrophe Is a Data-Policy Decision
Prefixing a value with an apostrophe may be part of a spreadsheet-specific mitigation. It also changes the exported data.
In our second path, every parsed field retained the apostrophe, including ordinary text and the header. None of the 13 parsed values exactly matched its input. We deliberately applied the prefix to every field to make that tradeoff observable; this is not a recommended universal sanitizer.
That distinction matters when a spreadsheet later feeds a database, a billing system or another parser. A visible value that looks acceptable to a reviewer may no longer be the original machine-readable value. Decide whether transformation is allowed before choosing a mitigation, and document the transformation for downstream consumers.
If CSV is mandatory, define the receiving application, import procedure and save/reopen behavior. Use a correct CSV serializer, then test the chosen mitigation in that environment with harmless fixtures. Do not infer a portable security guarantee from the CSV parser alone.
Why Ordinary XLSX Assignment Still Creates Formulas
XLSX can carry explicit cell types, but a library may infer those types from values. In the ordinary-assignment path, the two =1+1 cells became formulas: one header and one data value. #N/A became an error cell. The remaining ten cells were strings.
The openpyxl cell implementation documents the relevant binding behavior. Our installed-version result is the experiment above; the stable documentation is supporting implementation context, not a claim that all versions behave identically.
A value can therefore survive an exact comparison while its meaning changes. Reopening with data_only=False lets this test inspect the formula string rather than a cached result. Checking values alone would miss the boundary failure.
For a new text-only review workbook, our explicit path assigns the value and then sets cell.data_type = 's'. The ordering matters because ordinary assignment performs type binding first. The wrapper rejects excessive length and illegal control characters instead of silently accepting an altered export.
This narrow writer is not a sanitizer for arbitrary existing workbooks. It does not inspect every embedded object or safely preserve a workbook that mixes trusted formulas with untrusted data. If trusted formulas are required, define their cells separately and keep untrusted values out of that allowlist.
Reproduce the Harmless Four-Path Test
Run the following complete fixture in Python with openpyxl available. It creates files in memory, prints a JSON summary, and uses no production data, network requests, macro payloads or external formula targets. Expected acceptance output is passed: 14; generated ZIP hashes can vary between runs because package metadata can change.
Turn the Result Into a Delivery Contract
Before delivering a text-only workbook, require exact values for every expected header and cell, string types for untrusted text, no unexpected formula nodes, and the absence of prohibited external relationships or macro parts. Reject unsupported input rather than quietly changing it. Also verify sheet names, expected dimensions and omitted rows for the real export; those requirements were not all acceptance assertions in this small experiment.
Next, open a harmless fixture in the actual receiving application. Inspect its behavior, save, close and reopen it, and check any subsequent CSV conversion. Re-exporting to CSV removes the explicit XLSX string-type contract, so it needs a new acceptance test. OWASP's save/reopen caveat is a reason to test that complete handoff rather than just the first import.
Do not run adversarial formulas against a production workstation to reproduce this article. The harmless values above are enough to expose serialization and type-inference differences without demonstrating exfiltration or command execution.
Run the Fixture Through Aident Loadout
For an agent that needs a hosted Python experiment, first choose its execution boundary. Code Interpreter vs AI Agent Sandbox explains why a data-analysis runtime and a broader workspace are different choices.
Give the agent this bounded task:
Follow https://aident.ai/SETUP.md. Discover the current Code Interpreter execution Action, inspect its schema and preflight the complete synthetic fixture above. Run only that fixture. Return the execution wrapper, Python error, stderr and JSON checks. Do not upload customer data, open a spreadsheet application, execute formulas or send external requests. Accept the result only when the code error and stderr are empty and all 14 checks pass.
Our September 30 call used code_to_execute, timeout: 60 and keep_alive: 0. The exact preflight quoted zero credits for that call; recheck current pricing, packages and schema before another execution. An Action wrapper reporting success is not sufficient if the returned code error or assertions indicate failure.
Before moving from a synthetic experiment to business data, review permission, retention and data-sharing boundaries. Giving an AI agent API access without exposing keys covers the separate credential-access decision. This article does not establish permission to upload a private spreadsheet.
Inspect the Export Test Runtime
Limits, Sources and Refresh Triggers
This experiment establishes a narrow distinction between syntax, value preservation and cell typing. It does not establish universal formula-injection prevention, full workbook security, compatibility across spreadsheet applications, model safety or product conversion. It also does not cover arbitrary inherited workbook features or all Unicode and locale behavior.
Primary sources inspected September 30, 2026:
OWASP CSV Injection: the spreadsheet interpretation boundary and mitigation caveats.
openpyxl Cell implementation and Cell API: value binding and cell-type context.
ihub-apps export fix, PR 1898: a current application-owned CSV/XLSX export change, not a cross-vendor benchmark.
The complete first-party fixture is inline above. Rerun it when the library, hosted runtime or export policy changes. Add application-specific save/reopen tests when the receiving spreadsheet or import settings change. Expand the corpus before expanding the claim.
About the author

Todd Mercer
Todd Mercer is a senior engineer with more than 10 years of experience working on social media platforms and large-scale ordering systems. He graduated from the University of Illinois Urbana-Champaign in computer science. At Aident, he covers technical issues, developer tools, and technology topics.



The one tool
for every tool
your agent needs.
Give any AI agent real capabilities in seconds. Connect 27,000+ tools once, skip the setup headache, and let your agents execute.


