T09 · Insecure Skill Coding Practices
- Location
scripts/tracker.py:62- Finding
CSV Formula Injection Through Attacker-Controlled Sleep Notes
- Content
View full analysis
None: with open(LOG_FILE, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=CSV_FIELDS) writer.writeheader() writer.writerows(rows) ``` ```python def parse_event(text: str) -> ParsedEvent: raw = normalize_ws(text) lowered = raw.lower() if any(phrase in lowered for phrase in ["fell asleep", "falling asleep", "asleep around", "finally asleep", "passed out", "went to sleep"]): return ParsedEvent("sleep", ACTION_DISPLAY["sleep"], raw) ``` All classification branches preserve `raw` as the notes value, including the fallback branch: ```python return ParsedEvent("note", ACTION_DISPLAY["note"], raw) ``` The untrusted notes value is then inserted into the CSV row: ```python return { "entry_id": next_entry_id(rows), "timestamp_utc": dt_utc.astimezone(timezone.utc).isoformat().replace("+00:00", "Z"), "date_local": dt_local.strftime("%Y-%m-%d"), "time_local": format_time_local(dt_local), "timezone": str(TIMEZONE), "action": event.action, "emoji": event.emoji, "notes": event.notes, ``` ### Technical Analysis The tracker accepts raw Discord message text, normalizes whitespace, and stores the resulting string in the `notes` column of `sleep_log.csv`. It does not neutralize values beginning with spreadsheet formula indicators such as `=`, `+`, `-`, or `@`. The Python CSV writer correctly escapes CSV syntax, but CSV quoting is not a defense against formula injection. When the resulting file is opened or imported into formula-capable spreadsheet software, a cell beginning with a dangerous prefix may be interpreted as a ...[truncated 1965 chars]- Remediation
View remediation
str: if not isinstance(value, str): return value candidate = value.lstrip() if candidate.startswith(("=", "+", "-", "@")): return "'" + value return value ``` Apply it before serialization: ```python def write_rows(rows: list[dict]) -> None: safe_rows = [ {key: sanitize_csv_cell(value) for key, value in row.items()} for row in rows ] with open(LOG_FILE, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=CSV_FIELDS) writer.writeheader() writer.writerows(safe_rows) ``` If exact raw-message preservation is required, store canonical records in JSON or another non-formula format and generate a separately sanitized CSV export for spreadsheet use. ]]>
