T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/csv_builder.py:53
- Finding
- CSV Formula Injection in Spreadsheet-Bound Lead Exports<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/csv_builder.py:53-54` - `scripts/sheets_prep.py:39-43` **Vulnerability Type**: CSV/Spreadsheet Formula Injection **Risk Level**: Medium ### Vulnerable Code `scripts/csv_builder.py:53-54`: ```python clean = {field: row.get(field, "") for field in FIELDS} writer.writerow(clean) ``` `scripts/sheets_prep.py:39-43`: ```python output_path.parent.mkdir(parents=True, exist_ok=True) with output_path.open("w", encoding="utf-8", newline="") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) ``` ### Technical Analysis Both scripts write externally supplied lead data to CSV without neutralizing values that spreadsheet applications may interpret as formulas. The affected values can originate from input JSON, source CSV files, user-provided records, or collected LinkedIn profile information. A string beginning with a spreadsheet formula indicator such as `=`, `+`, `-`, or `@` may be evaluated when the resulting CSV is opened or imported into compatible spreadsheet software. Leading whitespace, tabs, or carriage returns may also be used to bypass simplistic prefix checks in some spreadsheet environments. CSV quoting performed by `csv.DictWriter` only preserves CSV structure. It does not prevent a spreadsheet application from interpreting a quoted cell as a formula. Because the project's documented workflow explicitly prepares files for Google Sheets and spreadsheet review, opening or importing generated output is an expected operation rather than an unlikely misuse. ### Attack Path 1. An attacker places a formula payload in a lead-controlled field, such as `company`, `full_name`, `title`, `personalization_note`, or `message_v1`. 2. The malicious value is included in an input JSON file processed by `csv_builder.py`, or in a CSV file processed by `sheets_prep.py`. 3. The scripts preserve the value and write it directly to the output CSV. 4. A ...[truncated 1359 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Apply a centralized spreadsheet-cell sanitizer to every externally controlled string before writing CSV output. 1. Detect dangerous formula prefixes after accounting for leading spaces, tabs, carriage returns, and line feeds. 2. Neutralize suspicious values by prefixing them with an apostrophe or by using another escaping strategy documented as safe for every supported spreadsheet application. 3. Apply sanitization in both `csv_builder.py` and `sheets_prep.py`; sanitizing only one workflow leaves the other exploitable. 4. Preserve the original raw value separately only if necessary, and do not place unsanitized raw values in spreadsheet-bound output. 5. Document that generated files contain untrusted lead data and must not be configured to bypass spreadsheet security warnings. 6. Add automated tests for values beginning with `=`, `+`, `-`, and `@`, including variants preceded by whitespace, tabs, carriage returns, or line feeds. 7. Test benign numeric and textual values to ensure that sanitization does not unnecessarily alter legitimate data. Example defensive helper: ```python FORMULA_PREFIXES = ("=", "+", "-", "@") LEADING_CONTROL_CHARS = " \t\r\n" def sanitize_spreadsheet_cell(value): if not isinstance(value, str): return value candidate = value.lstrip(LEADING_CONTROL_CHARS) if candidate.startswith(FORMULA_PREFIXES): return "'" + value return value ``` Use the helper for every output field: ```python clean = { field: sanitize_spreadsheet_cell(row.get(field, "")) for field in FIELDS } writer.writerow(clean) ``` Before calling `writer.writerows(rows)` in `sheets_prep.py`, sanitize every value in every row using the same helper. ]]>
