T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/audit_logger.py:291
- Finding
- Spreadsheet Formula Injection in Audit Log Exports<![CDATA[ ## Vulnerability Details **File Location**: `scripts/audit_logger.py:291-309` and `scripts/audit_logger.py:333-348` **Vulnerability Type**: CSV/XLSX formula injection **Risk Level**: Medium ### Vulnerable Code ```python def _export_csv(self, logs: List[LogEntry], filepath: Path): """Export as CSV""" if not logs: return with open(filepath, 'w', newline='', encoding='utf-8') as f: # Collect all possible fields all_keys = set() for log in logs: all_keys.update(log.to_dict().keys()) all_keys.update(log.details.keys()) fieldnames = ['timestamp', 'datetime', 'operation', 'task_id'] + sorted( all_keys - { 'timestamp', 'datetime', 'operation', 'task_id', 'details' } ) writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() for log in logs: row = log.to_dict() row.update(log.details) row.pop('details', None) writer.writerow(row) ``` ```python # Data for log in logs: row = [ datetime.fromtimestamp(log.timestamp).strftime('%Y-%m-%d %H:%M:%S'), log.operation, log.task_id, json.dumps(log.details, ensure_ascii=False) ] ws.append(row) # Adjust column widths ws.column_dimensions['A'].width = 20 ws.column_dimensions['B'].width = 15 ws.column_dimensions['C'].width = 30 ws.column_dimensions['D'].width = 60 wb.save(filepath) ``` ### Technical Analysis The audit APIs accept caller-controlled strings, including task identifiers, exception messages, error messages, failed-step names, and tool names. These values are exported directly to CSV or XLSX files without neutralizing spreadsheet formula prefixes. Spreadsheet programs may interpret cells beginning with `=`, `+`, `-`, or `@` as formulas rather than plain text. In the XLSX path, `log.task_id` is inserted directly into a worksheet cell. In the CSV path, all ...[truncated 1780 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Sanitize every value before writing it to CSV or XLSX. 2. Convert values to strings and prefix values beginning with `=`, `+`, `-`, or `@` with a single quote. 3. Account for leading whitespace, tabs, carriage returns, and line feeds before checking the first effective character. 4. For XLSX exports, explicitly store untrusted values as strings rather than formulas. 5. Apply sanitization to both top-level fields and values serialized from `log.details`. 6. Add automated tests for every dangerous prefix and for values containing leading whitespace. Example defensive helper: ```python def _spreadsheet_safe(value: Any) -> str: text = "" if value is None else str(value) effective = text.lstrip(" \t\r\n") if effective.startswith(("=", "+", "-", "@")): return "'" + text return text ``` Apply this helper to every CSV field: ```python safe_row = { key: self._spreadsheet_safe(value) for key, value in row.items() } writer.writerow(safe_row) ``` For XLSX, sanitize each string before appending and explicitly set cells to string values where practical. Do not rely solely on spreadsheet-client warnings. ]]>
