T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/coffee_prices.py:206
- Finding
- Spreadsheet Formula Injection in CSV Output## Vulnerability Details **File Location**: `scripts/coffee_prices.py`, lines 206-213 **Vulnerability Type**: CSV/spreadsheet formula injection **Risk Level**: Medium ### Vulnerable Code ```python def output_csv(rows: List[CoffeePriceRow]) -> None: writer = csv.writer(os.sys.stdout) writer.writerow( ["brand", "brand_en", "city", "drink", "drink_en", "price", "currency"] ) for row in rows: writer.writerow( [row.brand, row.brand_en, row.city, row.drink, row.drink_en, row.price, row.currency] ) ``` ### Technical Analysis The `city` value can originate directly from the `--city` command-line argument or the `OPENCLAW_CITY` environment variable. It is retained in `row.city` and written to CSV without neutralizing spreadsheet formula prefixes. The Python CSV writer correctly quotes and escapes CSV syntax, but CSV quoting does not stop spreadsheet software from treating a cell beginning with `=`, `+`, `-`, or `@` as a formula. Consequently, an attacker-controlled city such as `=HYPERLINK(...)` may be evaluated when the resulting file is opened in a spreadsheet application. ### Attack Path 1. An attacker influences the city through `--city` or `OPENCLAW_CITY`. 2. The script passes the value through `resolve_city()` and `build_rows()` without formula-prefix validation. 3. The user selects CSV output, for example: ```bash python3 scripts/coffee_prices.py --city '=HYPERLINK("https://attacker.example","Click")' --output csv ``` 4. `output_csv()` writes the attacker-controlled value into the `city` column. 5. A victim opens the generated CSV in spreadsheet software. 6. Depending on the spreadsheet application and its security settings, the cell may be interpreted as a formula, potentially causing an external request or presenting deceptive content. ### Impact Assessment Exploitation does not grant privileges within the Python process itself. The i ...[truncated 452 chars]
- Remediation
- ## Remediation Suggestions Sanitize every externally controllable string before writing it to CSV. At minimum, neutralize values whose first non-whitespace character is `=`, `+`, `-`, or `@`. Prefixing such values with an apostrophe is commonly used when spreadsheet compatibility is required. ```python def sanitize_csv_cell(value: object) -> object: if not isinstance(value, str): return value stripped = value.lstrip() if stripped.startswith(("=", "+", "-", "@")): return "'" + value return value def output_csv(rows: List[CoffeePriceRow]) -> None: writer = csv.writer(os.sys.stdout) writer.writerow( ["brand", "brand_en", "city", "drink", "drink_en", "price", "currency"] ) for row in rows: writer.writerow( [ sanitize_csv_cell(row.brand), sanitize_csv_cell(row.brand_en), sanitize_csv_cell(row.city), sanitize_csv_cell(row.drink), sanitize_csv_cell(row.drink_en), row.price, sanitize_csv_cell(row.currency), ] ) ``` Apply the safeguard to all string columns rather than only `city`, so future changes do not reintroduce the issue. Add tests covering leading formula characters, leading whitespace, tabs, carriage returns, and ordinary city names. If strict input validation is acceptable, reject city values containing control characters or beginning with spreadsheet formula markers.
