T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/merge_results.py:89
- Finding
- Spreadsheet Formula Injection in Generated CSV Output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/merge_results.py`, lines 89-116 **Vulnerability Type**: CSV/spreadsheet formula injection **Risk Level**: Medium ### Vulnerable Code ```python row = {"slug": slug, "name": t.get("name", data.get("target", "")), "scenario": scenario} for k in fields: v = fd.get(k) if isinstance(v, (list, dict)): row[k] = json.dumps(v, ensure_ascii=False) else: row[k] = "" if v is None else v row["n_sources"] = len(data.get("sources") or []) rows.append(row) records.append(data) out_json = run / "merged.json" out_csv = run / "merged.csv" out_json.write_text( json.dumps({"scenario": scenario, "fields": fields, "records": records}, ensure_ascii=False, indent=2), encoding="utf-8") if rows: cols = ["slug", "name", "scenario"] + fields + ["n_sources"] with out_csv.open("w", newline="", encoding="utf-8-sig") as fh: w = csv.DictWriter(fh, fieldnames=cols) w.writeheader() for r in rows: w.writerow(r) ``` ### Technical Analysis The merger writes externally derived target names and worker-provided field values directly to `merged.csv`. Python's `csv` module performs CSV quoting but does not neutralize spreadsheet formulas. If a textual cell begins with a formula marker such as `=`, `+`, `-`, or `@`, spreadsheet applications may interpret the value as a formula rather than inert text. Leading whitespace, tab characters, or carriage returns can also be used to evade simplistic prefix checks in some spreadsheet clients. The documented workflow encourages users to open the resulting UTF-8-SIG CSV in Excel. Consequently, malicious content copied by a research worker from an untrusted web source can cross the data-to-executable-formula boundary when the report is opened. ### ...[truncated 1453 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Treat every textual CSV cell as untrusted, including `slug`, `name`, `scenario`, and all dynamically selected fields. 1. Add a centralized CSV-cell sanitization function. 2. Remove or reject leading control characters used to hide formula prefixes. 3. Prefix cells beginning with `=`, `+`, `-`, or `@` with a single quote so spreadsheet applications treat them as text. 4. Apply the function immediately before every value is passed to `csv.DictWriter`. 5. Preserve the original unsanitized values only in `merged.json`, with clear documentation that JSON consumers must treat them as untrusted. 6. Add tests for ordinary formulas and whitespace-, tab-, and carriage-return-prefixed variants. Example hardening: ```python DANGEROUS_FORMULA_PREFIXES = ("=", "+", "-", "@") def safe_csv_cell(value): if value is None: return "" text = str(value) inspected = text.lstrip(" \t\r\n") if inspected.startswith(DANGEROUS_FORMULA_PREFIXES): return "'" + text return text for row in rows: w.writerow({key: safe_csv_cell(value) for key, value in row.items()}) ``` If exact preservation of values is required, prefer a spreadsheet-generation library that can explicitly mark every untrusted cell as a string. ]]>
