-
Notifications
You must be signed in to change notification settings - Fork 0
Sanitize all CSV export cells against formula injection (CWE-1236) #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,30 +27,37 @@ import { saveAs } from 'file-saver'; | |
| * Neutralize spreadsheet formula injection (CSV injection) for a cell value by | ||
| * prefixing with a single quote when the value could be interpreted as a | ||
| * formula (leading =, +, -, @, tab, CR, optionally after whitespace). | ||
| * Numeric values (e.g. negative telemetry readings like -273.15) are returned | ||
| * unchanged so spreadsheets still treat them as numbers. | ||
| * @see https://owasp.org/www-community/attacks/CSV_Injection | ||
| * @param {*} value | ||
| * @returns {*} | ||
| */ | ||
| export function sanitizeCsvFormulaInjection(value) { | ||
| if (value === null || value === undefined) { | ||
| if (value === null || value === undefined || typeof value === 'number') { | ||
| return value; | ||
| } | ||
|
|
||
| const str = String(value); | ||
| if (/^\s*[=+\-@\t\r]/.test(str)) { | ||
| const trimmed = str.trim(); | ||
| if (trimmed !== '' && Number.isFinite(Number(trimmed))) { | ||
| return value; | ||
| } | ||
|
|
||
| return `'${str}`; | ||
| } | ||
|
|
||
| return str; | ||
| return value; | ||
| } | ||
|
|
||
| /** | ||
| * Encodes tabular data as CSV and triggers a browser download via FileSaver. | ||
| * | ||
| * This layer does not sanitize cell values or filenames. Any user-controlled text | ||
| * (including Open MCT object `name` fields shown in exported rows) should be passed | ||
| * through {@link sanitizeCsvFormulaInjection} where spreadsheet tools could treat | ||
| * leading `=`, `+`, etc. as formulas. | ||
| * Every exported cell is passed through {@link sanitizeCsvFormulaInjection} so | ||
| * user-controlled text (object names, string telemetry values, unit metadata) | ||
| * cannot be interpreted as a spreadsheet formula (leading `=`, `+`, `-`, `@`, | ||
| * tab, or CR). | ||
| */ | ||
| class CSVExporter { | ||
| /** | ||
|
|
@@ -62,7 +69,15 @@ class CSVExporter { | |
| export(rows, options) { | ||
| let headers = (options && options.headers) || Object.keys(rows[0] || {}).sort(); | ||
| let filename = (options && options.filename) || 'export.csv'; | ||
| let csvText = new CSV(rows, { header: headers }).encode(); | ||
| let sanitizedRows = rows.map((row) => { | ||
| let sanitizedRow = {}; | ||
| headers.forEach((header) => { | ||
| sanitizedRow[header] = sanitizeCsvFormulaInjection(row[header]); | ||
| }); | ||
|
|
||
| return sanitizedRow; | ||
| }); | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
| let csvText = new CSV(sanitizedRows, { header: headers }).encode(); | ||
|
Comment on lines
+72
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Sanitized rows only carry header keys; equivalent to prior header-filtered output
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| let blob = new Blob([csvText], { type: 'text/csv' }); | ||
| saveAs(blob, filename); | ||
| } | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 Info: Numeric-string exemption keeps leading +/- values unescaped by design
The new numeric guard (
sanitizeCsvFormulaInjectionatsrc/exporters/CSVExporter.js:43-46) returns cells like-273.15,+1,-1unchanged whenNumber.isFinite(Number(trimmed))is true, even though they begin with a formula-trigger character (+/-). This is an intentional tradeoff to keep numeric telemetry readable as numbers in spreadsheets, and it is safe because a purely finite-numeric string cannot form a malicious formula (values like-1+1,=cmd,@xyield NaN and remain escaped). Worth noting for reviewers that this deviates from the strict OWASP rule of escaping every cell starting with=+-@, but poses no practical injection risk.Was this helpful? React with 👍 or 👎 to provide feedback.