T09 · Insecure Skill Coding Practices
- Location
scripts/itinerary_exporter.py:40- Finding
CSV Formula Injection in Itinerary Export
- Content
View full analysis
Vulnerability Details
File Location:
scripts/itinerary_exporter.py, lines 40-54; untrusted JSON is loaded at lines 67-68
Vulnerability Type: CSV formula injection
Risk Level: MediumVulnerable Code
python with open(output_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow(['Day', 'Date', 'Time', 'Activity', 'Type', 'Duration', 'Cost', 'Rating']) for day in itinerary.get('itinerary', []): for activity in day['activities']: writer.writerow([ day['day'], day['date'], activity['time'], activity['name'], activity['type'], activity.get('duration', ''), activity.get('cost', ''), activity.get('rating', ''), ])The data source is an arbitrary JSON file:
python with open(args.input, 'r', encoding='utf-8') as f: itinerary = json.load(f)Technical Analysis
The exporter writes fields from an externally supplied itinerary directly into CSV cells without neutralizing spreadsheet formula prefixes. Python's
csv.writercorrectly quotes CSV syntax, but quoting does not prevent spreadsheet applications from interpreting cell contents as formulas.Attacker-controlled fields such as
activity['name'],activity['type'],activity['time'], and related values can begin with formula-triggering characters such as=,+,-, or@. Tabs and carriage returns may also be treated as dangerous prefixes by some spreadsheet applications.For example, a malicious itinerary could contain:
json { "destination": "Example", "days": 1, "total_estimated_cost": 0, "itinerary": [ { "day": 1, "date": "2026-09-16", "activities": [ { "time": "09:00", "name": "=HYPERLINK(\"https://attacker. ...[truncated 1931 chars]- Remediation
View remediation
Remediation Suggestions
- Sanitize every untrusted value before passing it to
writer.writerow. After accounting for leading whitespace and control characters, neutralize values beginning with=,+,-, or@. - Prefix dangerous values with an apostrophe so spreadsheet applications treat them as text. Apply this consistently to all string-capable columns, not only activity names.
- Reject or remove leading tab, carriage-return, newline, and other control characters that can bypass simple first-character checks.
- Validate the imported JSON against a strict schema, including expected types, maximum lengths, required fields, and allowed formats.
- Document that imported itinerary files are untrusted and warn users before exporting externally supplied content to spreadsheet-compatible formats.
- Add regression tests covering formula prefixes, leading whitespace, tabs, carriage returns, and nested attacker-controlled fields.
Example hardening approach:
python DANGEROUS_CSV_PREFIXES = ('=', '+', '-', '@', '\t', '\r', '\n') def sanitize_csv_cell(value): if not isinstance(value, str): return value if value.startswith(DANGEROUS_CSV_PREFIXES): return "'" + value stripped = value.lstrip(' \t\r\n') if stripped.startswith(DANGEROUS_CSV_PREFIXES): return "'" + value return valueApply
sanitize_csv_cellto every value written to a CSV row. If preserving exact values is unnecessary, a stricter allowlist-based validation policy is preferable.- Sanitize every untrusted value before passing it to
