T09 · Insecure Skill Coding Practices
- Location
scripts/coffee_prices.py:206- Finding
Spreadsheet Formula Injection in CSV Output
- Content
View full analysis
Vulnerability Details
File Location:
scripts/coffee_prices.py, lines 206-213
Vulnerability Type: CSV/spreadsheet formula injection
Risk Level: MediumVulnerable 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
cityvalue can originate directly from the--citycommand-line argument or theOPENCLAW_CITYenvironment variable. It is retained inrow.cityand 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
- An attacker influences the city through
--cityorOPENCLAW_CITY. - The script passes the value through
resolve_city()andbuild_rows()without formula-prefix validation. - The user selects CSV output, for example:
bash python3 scripts/coffee_prices.py --city '=HYPERLINK("https://attacker.example","Click")' --output csv output_csv()writes the attacker-controlled value into thecitycolumn.- A victim opens the generated CSV in spreadsheet software.
- 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]
- An attacker influences the city through
- Remediation
View 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.
