An export has two attack surfaces. The first is the server that builds the file, and it is the one everybody guards. The second is the program that opens it, and quoting a field correctly does nothing for that one, because a spreadsheet reads the first character of a cell as an instruction.
Two surfaces, and only one of them is the server’s
A CSV standard is a parsing contract. It says what to do when a value contains a comma, a double quote or a line break: wrap the field, double any quote inside it, and the row survives the trip. Every export writer implements that, and a team that implements it correctly can watch its file open anywhere with the columns intact.
None of it is about the second surface. The program at the other end is not reading a document, it is building a sheet, and before it stores a cell it decides whether that cell holds a value or an instruction. It decides from the first character. A cell reading =1+1 contains no comma, no quote and no line break, so the parsing contract has no opinion about it: the field is written out unquoted, correct by every rule the standard states, and the sheet evaluates it on open.
The two guards therefore do different jobs, and one is not a relaxed version of the other. Quoting protects the file. Neutralizing protects the person who opens it.
Where a stranger’s text becomes the title
For a feedback tool this matters more than it does for a report generator, because nobody on the project writes the text.
A widget on a public page takes a report from whoever loaded that page. There is no account behind the submission, and unless the host page vouches for somebody, nothing about the person is proved: it carries the project’s public key, which sits in the page source by design, and the endpoint answers cross-origin because an embedded widget cannot work otherwise. What bounds it is a size cap and a rate limit, the same shape of ceiling the host isolation contract puts on everything else that tag does.
Then the words move on their own. For a report arriving this way, nobody on the team types the title: the server trims what was submitted, takes its first line and cuts it to a fixed length. Three months later somebody in support filters the quarter, presses export and opens the file, and the reporter’s own first line is sitting in one of the leading columns.
| What arrives | What bounds it |
|---|---|
| A report from anybody who can load the page | the project’s public key, which the page publishes |
| Repeated submissions from one session or address | ten an hour |
| The body of the report | 10,000 characters |
| The title of the ticket | the first line of that body, cut at 80 characters |
None of those bounds is about content. They cap how much and how often, which is the right job for a rate limit and the wrong tool for this one: a formula that opens a URL fits comfortably inside every row.
Escaping and neutralizing are different jobs
The characters a CSV standard forces quoting on are the comma, the double quote, the carriage return and the line feed, and that set exists to stop a row from splitting. The set a spreadsheet treats as the start of a formula is a different one, and the two overlap only in part: some formula characters do force quoting, and an equals sign does not. That is the entire mechanism, and it is why a correct exporter can be a dangerous one. A cell that is nothing but a formula satisfies every rule the standard states, arrives unquoted, and runs.
The answer is a second pass over the leading character of a text cell, run before the quoting decision. What makes it interesting is what that pass has to refuse to do.
| Cell text | What a correct exporter does | Why |
|---|---|---|
=1+1 | neutralize it before it reaches the sheet | an equals sign triggers none of the quoting the standard forces |
a=b | leave it exactly as written | only the first character decides, and treating every = as a formula rewrites ordinary prose |
-42, held as a number | leave it a number, unquoted | a negative number opens with a formula character, and prefixing it turns a numeric column into text |
OWASP’s page on CSV injection offers, among other approaches, a sanitization applied to each field of the file: wrap it in double quotes, prepend a single quote, escape the quotes inside. Applied to every field as written, the third row above is what that costs, and the failure is silent. Nothing errors, the file opens, the columns look right, and the total at the bottom of a numeric column comes back as zero because every value above it is now a string. An export can be safe and useless at the same time, and the person who finds out is the one who trusted the number.
Who gets to press export
Reading one ticket and downloading every ticket are separate acts here, and the permission says so.
Opening the board is the lowest role that has any access to a project at all. The export sits one rank above it and then asks a second question, about the plan, before a single row is selected. The reasoning is that bulk egress of personal data is a different operation from the per-ticket reads somebody with read access already performs, however many of them they perform.
The order of those two checks carries its own argument. The role check runs before any query, so a refusal cannot teach somebody outside the project that the project exists: they get the answer they would get for a project nobody ever created. The entitlement check runs after it and still ahead of the first row, so a plan refusal lands on an empty response rather than partway through a file of personal data. What the security page covers is the same data arriving; this is the same data leaving.
The question that transfers to any other tool is whether it draws that line at all. Where one role both opens a ticket and downloads ten thousand of them, an export is being treated as a read that happens to be large.
What escaping at export time does not give you
The larger limit belongs to the spreadsheet rather than to any exporter. The OWASP page linked above warns that Excel may remove quotes or escape characters when a file is saved and re-opened, so previously escaped formulas can become active again. A file that has been round-tripped through a spreadsheet is not a sanitized file, whoever wrote it originally. Neutralizing at export time narrows the second surface and does not close it.
That page also maintains the character class itself, width variants included, and it is the list to audit an exporter against rather than one to transcribe into an article. It ends on the sentence worth carrying away: there is no universal CSV sanitization strategy that is safe for every spreadsheet application and every downstream consumer. Its own Excel-resistant advice is a leading tab inside the quoted field, and other guidance treats a leading tab as the thing to neutralize. Whoever picks between them is picking which consumer to protect.
What a neutralized value carries afterwards is our own trade to declare. The marker is a character in the file, so anything reading the export as data sees it, and how a given spreadsheet chooses to display it is that program’s own business. It appears only on the values that needed it, which is the awkward half for whoever automates: the leading character has to come off conditionally, on the cells carrying it, rather than uniformly down a column. We took that trade deliberately. A marker somebody has to strip is the cheaper of the two failures, and the expensive one runs on their machine.