T09 · Insecure Skill Coding Practices
- Location
scripts/parse-invoice.py:82- Finding
Spreadsheet Formula Injection Through Unsanitized Invoice Data
- Content
View full analysis
Vulnerability Details
File Location:
scripts/parse-invoice.py:82-86,scripts/parse-invoice.py:180-205, andscripts/parse-invoices.py:69-91
Vulnerability Type: CSV/spreadsheet formula injection
Risk Level: MediumTechnical Analysis
Text extracted from an untrusted PDF is assigned directly to invoice fields. In particular, the first extracted line becomes the vendor name without any validation or spreadsheet-specific escaping:
python # Vendor name (usually at top) lines = text.strip().split('\n') if lines: data['vendor_name'] = lines[0].strip()The single-invoice writer then places the attacker-controlled fields directly into CSV cells:
python def write_csv(data, output_path): """Write invoice data to CSV.""" fieldnames = ['vendor_name', 'invoice_number', 'invoice_date', 'due_date', 'description', 'quantity', 'unit_price', 'line_total', 'subtotal', 'tax', 'total', 'currency'] with open(output_path, 'w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() for item in data['line_items']: row = { 'vendor_name': data['vendor_name'], 'invoice_number': data['invoice_number'], 'invoice_date': data['invoice_date'], 'due_date': data['due_date'], 'description': item.get('description', ''), 'quantity': item.get('quantity', ''), 'unit_price': item.get('unit_price', ''), 'line_total': item.get('line_total', ''), 'subtotal': data['subtotal'], 'tax': data['tax'], 'total': data['total'], 'currency': data['currency'] } writer.writerow(row)The consolidated writer has the same vulnerable data flow:
py ...[truncated 2629 chars]- Remediation
View remediation
Remediation Suggestions
- Introduce a centralized sanitizer for every textual value written to CSV.
- Treat cells beginning with
=,+,-, or@as unsafe after removing or examining leading whitespace and control characters. - Neutralize unsafe values according to the target spreadsheet's documented import behavior, commonly by prefixing them with an apostrophe.
- Apply sanitization at the output boundary to all string fields, including vendor names, invoice numbers, descriptions, dates, and currency values.
- Preserve an unsanitized structured format such as JSON only when consumers explicitly require original extracted text and understand that it is untrusted.
- Add regression tests covering all formula prefixes, leading spaces, tabs, carriage returns, and line feeds.
- Warn users that generated files contain untrusted invoice content and should be imported with formula execution disabled.
Example hardening pattern:
python FORMULA_PREFIXES = ('=', '+', '-', '@') def sanitize_spreadsheet_cell(value): value = '' if value is None else str(value) inspected = value.lstrip(' \t\r\n') if inspected.startswith(FORMULA_PREFIXES): return "'" + value return valueApply this function to every value in
rowbefore callingwriter.writerow(row).
