Quick answer: Before sharing a JSON-to-CSV export, test six boundaries: untrusted values and headers, delimiter-safe serialization, the chosen formula-handling policy, the real spreadsheet import path, save/reopen behavior, and downstream data fidelity. Correct JSON and well-formed CSV do not establish that the receiving spreadsheet will treat every cell as harmless text.
A support report looks like ordinary rows until an imported customer name becomes a calculated cell. The exporter produced valid CSV; the spreadsheet assigned a meaning the application never intended.
This is a release checklist for spreadsheet-facing exports, not a new conversion tutorial. Use the JSON-to-CSV methods guide for flattening and conversion approaches. A PrestaShop advisory published August 18, 2026 illustrates that formula handling remains a practical export concern. It is evidence about that product's affected workflow, not a claim that all CSV libraries are vulnerable.
1. Can untrusted content reach any exported cell or header?
Trace every exported value and column label back to its source, including imported records, integration data, custom fields, and dynamically generated headers. Test harmless formula-like strings in each untrusted path. A check limited to the main description field misses other cells that the spreadsheet can interpret after conversion or flattening.
Start with a concrete export inventory. Name who supplies the value, who downloads the file, and which application opens it. An administrator-facing export is not automatically trusted simply because only administrators can click its download button. Its contents can still originate from customer profiles or a partner feed.
MITRE's CWE-1236 description identifies the boundary as spreadsheet interpretation of exported user-controlled information. Keep the initial fixture harmless: a short arithmetic expression is sufficient to distinguish literal text from a calculated result. There is no need for a command, network request, or external destination.
Dynamic JSON keys deserve review too. A flattening function can promote a user-controlled field name into a CSV header. Check labels as well as row values, and test nested objects with names that resemble application-owned columns. The purpose is to understand the final table, not just the original JSON shape.
import assert from 'node:assert/strict';
const row = { '=1+1': 'custom heading', note: '=1+1', amount: -12.5 };
const review = [];
for (const [key, value] of Object.entries(row)) {
if (key.startsWith('=')) review.push({ kind: 'header', key });
if (typeof value === 'string' && value.startsWith('=')) {
review.push({ kind: 'text', key });
}
}
assert.deepEqual(review.map(x => x.kind), ['header', 'text']);
This fixture only demonstrates inventory coverage for one marker. It is not a sanitization function or a complete formula-prefix detector. Maintain an application-specific corpus covering the receiving environments, and ensure new export columns enter that review rather than inheriting an undocumented assumption of trust.
2. Do quotes and delimiters preserve the intended cell boundaries?
Round-trip values containing delimiters, double quotes, carriage returns, and line feeds through the selected CSV writer and parser. Assert the exact number of rows and columns and the recovered field values. Structural quoting prevents accidental cell splitting, but a correctly preserved cell can still contain formula-like text requiring a separate policy.
Do not build CSV with a simple comma join. A customer note containing a comma is one field, not a request to add another column. A newline inside that note should remain part of the field rather than becoming a new customer row. The RFC 4180 description documents conventional quoting and doubled embedded quotes; it does not define a spreadsheet security policy.
The following executable fixture uses Papa Parse to test serialization boundaries. It deliberately preserves a formula-like value so the distinction stays visible:
import assert from 'node:assert/strict';
import Papa from 'papaparse';
const rows = [['label', 'note'], ['Sample', 'comma, quote " and\nnewline'],
['Literal fixture', '=1+1']];
const csv = Papa.unparse(rows, { quotes: true, newline: '\r\n' });
const parsed = Papa.parse(csv, { skipEmptyLines: false });
assert.equal(parsed.errors.length, 0);
assert.deepEqual(parsed.data, rows);
assert.equal(parsed.data[2][1], '=1+1');
Repeat structural tests for every delimiter you expose. A semicolon option changes which characters require protection. Test the actual exported artifact after any encoding or delivery wrapper, not only an intermediate string in a helper function.
For a quick structural preview, use the JSON-to-CSV converter with sanitized fixtures. Its implementation handles cell quoting and nested-row conversion; it does not claim to neutralize spreadsheet formulas. Keep that capability boundary explicit in documentation and user-facing export options.
3. Does the formula policy change only the intended values?
Apply a documented spreadsheet-facing text policy and assert its exact output for formula-like strings, ordinary text, blank values, and validated numeric amounts. Review what the policy changes rather than accepting a library option by name. Keep machine-oriented exports separate when a protective prefix would alter data another system expects to consume unchanged.
Papa Parse documents escapeFormulae as an output option that prefixes matching field values with an apostrophe. CSV Stringify documents a similar escape_formulas option. Neither option substitutes for testing the file's actual destination. Their documented prefix sets and your application's input policy need to be considered together.
import assert from 'node:assert/strict';
import Papa from 'papaparse';
const csv = Papa.unparse([
['=1+1', 'ordinary', '-12.50', -12.5]
], { escapeFormulae: true });
const values = Papa.parse(csv).data[0];
assert.equal(values[0], "'=1+1");
assert.equal(values[1], 'ordinary');
assert.equal(values[2], "'-12.50");
assert.equal(values[3], '-12.5');
This test demonstrates the installed library's transformation, not spreadsheet execution behavior. The numeric and string versions of the amount intentionally differ. A schema-owned numeric column should not become arbitrary text merely because a generic prefix check sees a minus sign; an untrusted text column should not become trusted by being coerced to a number.
Keep the original JSON unchanged. Generate the spreadsheet-facing representation at the export boundary and record which policy produced it. If a consumer needs exact text, offer a separately documented machine-data path instead of asking it to strip protective characters heuristically. Removing every leading apostrophe can corrupt legitimate names as well as undo a defense.
Include a regression fixture for previously transformed text. A report may be generated from an earlier import rather than directly from the original customer record. Decide whether the exporter receives canonical source values or an already transformed representation, and assert that contract at its boundary. Repeated transformations can accumulate visible prefixes, while aggressive cleanup can remove a legitimate character. An explicit representation flag or separate pipeline is easier to audit than guessing from the first character of the value.
4. Does the real spreadsheet import preserve your column contract?
Open the exported fixture through the same application and import path recipients use, with the intended delimiter, encoding, and column types. Record whether formula-like text stays text and whether identifiers remain intact. Double-click opening and explicit text import are different workflows, so passing one does not establish that the other is safe.
Microsoft's text-file import guidance describes Excel's import and delimiter choices. Test the supported workflow directly. A screenshot of the file in a text editor proves what was emitted, not how Excel interpreted it.
Include an identifier such as 00123, a long numeric-looking reference stored as a string, an international name, and a date-like label. These fixtures catch data interpretation errors that can coexist with formula handling. A report that avoids formulas but silently changes account identifiers still fails its export contract.
Keep test records synthetic and disable unnecessary external connections in the test environment. Inspect the cell's stored type or formula state as well as its displayed value. Display alone can be misleading: a calculated result and a literal result can look identical.
If your policy requires all columns to be imported as text, put that requirement in the supported workflow and test it with actual recipients' software. Do not describe the artifact as universally safe when safety depends on an import choice the application cannot enforce. Where the workflow needs stronger typing, consider a spreadsheet format with explicitly authored cell types and test that separate implementation.
5. Does protection survive saving, reopening, and sharing the file?
Save the imported fixture using the recipient's normal workflow, close it, reopen the saved artifact, and recheck formula state and literal values. Repeat after any supported format conversion or sharing step. A mitigation that works only on the first open is insufficient for a report routinely edited and redistributed by another team.
OWASP's CSV-injection guidance warns that spreadsheet save/reopen behavior can change the effectiveness of common escaping approaches. There is no universal CSV transformation guaranteed across every spreadsheet and downstream consumer. Qualify the tested applications and workflow instead of promising safety from a single prefix rule.
Use a short acceptance record: application version, locale, opening method, export policy, save format, reopened cell state, and expected literal value. Retain both the original and saved synthetic artifacts for comparison. If a regression appears after an application update, that record gives the team a reproducible starting point.
When testing variants, include the environments your users actually have rather than a large unsupported checklist. A finance team that edits in one spreadsheet and sends CSV to an importer needs both boundaries tested. A read-only browser preview followed by no download is a different product contract.
Do not respond to a failed reopen test by appending more invisible characters until one sample looks right. Such changes can alter identifiers and integrations. Revisit the file format, supported import path, or consumer contract, then retest the complete lifecycle with harmless fixtures.
6. Can downstream systems distinguish protected display data from original data?
Reimport the generated CSV with the actual downstream parser and compare its values against the intended export representation, not an assumed copy of the original JSON. Document protective prefixes, null handling, flattened columns, and type conversion. A spreadsheet-facing file can intentionally differ from source data, but that difference must remain explicit and testable.
Python's CSV documentation describes dialect settings and shows why reader and writer behavior must be specified. More generally, CSV background explains the format's tabular nature; it does not supply the type system of the original JSON. Do not assume every round trip is lossless.
This fixture makes an intentional display transformation visible:
import assert from 'node:assert/strict';
import Papa from 'papaparse';
const original = [{ reference: '00123', note: '=1+1' }];
const snapshot = JSON.stringify(original);
const csv = Papa.unparse(original, { escapeFormulae: true });
const recovered = Papa.parse(csv, { header: true, dynamicTyping: false });
assert.equal(recovered.errors.length, 0);
assert.equal(recovered.data[0].reference, '00123');
assert.equal(recovered.data[0].note, "'=1+1");
assert.equal(JSON.stringify(original), snapshot);
assert.notDeepEqual(recovered.data, original);
The right assertion is not always equality with the original. For a display export, verify the documented protected representation; for an integration export, verify its separate exact-data contract. If the same file serves both audiences, resolve that ambiguity before release rather than placing the burden on users.
Keep a manifest of column meanings and transformations beside the exporter. When adding a field, review whether it is untrusted text, validated numeric data, or an identifier that must remain a string. The CSV-to-JSON guide covers type inference on the return journey.
Run the acceptance test through the real download handler as well. Check that a failed export returns an error response instead of a partially generated report with a success filename. Verify that retries do not serve a previous user's cached artifact and that the downloaded columns match the authorized selection. These checks do not replace formula testing; they establish that the file under review is the intended complete artifact for the intended recipient.
All four JavaScript examples were executed locally. No Excel, LibreOffice, or Google Sheets execution test is claimed here. The examples establish inventory, CSV structure, transformation behavior, and data fidelity; the application-specific spreadsheet tests above remain required evidence before your own export is approved for sharing. Pair them with the JSON security review for the surrounding trust and data-handling boundaries.