- Location
- scripts/merge_results.py:83
- Finding
- Spreadsheet Formula Injection in Generated CSV Output<![CDATA[
## Vulnerability Details
**File Location**: `scripts/merge_results.py`, lines 83–106
**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
Target names, slugs, scenario values, and worker-generated field values are written directly to `merged.csv`. Worker fields can originate from externally controlled web pages or other researched material.
Spreadsheet applications may interpret cells beginning with formula-control characters—commonly `=`, `+`, `-`, or `@`—as executable formulas. The Python CSV writer correctly escapes CSV syntax, but CSV quoting does not reliably prevent Excel and similar applications from evaluating formulas.
Because the project explicitly produces a UTF-8-SIG CSV intended to open directly in Excel, spreadsheet interpretation is part of the expected workflow. A malicious source can therefore place a formula-like value in a researched field and have it carried into the generated CSV.
### Attack Path
1. An attacker controls content on a page, document, repository, or other object selected for research.
2.
...[truncated 1544 chars]
- Remediation
- <![CDATA[
## Remediation Suggestions
1. Neutralize all string cells before writing CSV, including metadata columns and dynamic fields:
```python
FORMULA_PREFIXES = ("=", "+", "-", "@")
def csv_safe(value):
if not isinstance(value, str):
return value
if value.lstrip().startswith(FORMULA_PREFIXES):
return "'" + value
return value
```
2. Apply the function to every value passed to `DictWriter`:
```python
for r in rows:
w.writerow({key: csv_safe(value) for key, value in r.items()})
```
3. Consider treating leading tabs, carriage returns, newlines, and other whitespace carefully because some spreadsheet applications ignore leading whitespace before identifying a formula. Normalize or inspect the first significant character.
4. Keep the original values unchanged in `merged.json` so the machine-readable artifact remains faithful to the research data. Formula neutralization should occur only at the spreadsheet export boundary.
5. Document that the CSV is a presentation artifact with formula-neutralized cells.
6. Add regression tests for values beginning with `=`, `+`, `-`, and `@`, including variants preceded by whitespace, tabs, carriage returns, or newlines.
]]>