T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/generate_estimation.py:78
- Finding
- Spreadsheet Formula Injection Through Untrusted Requirement Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_estimation.py:78-85`, with untrusted values reaching the sink at `scripts/generate_estimation.py:185-186`, `scripts/generate_estimation.py:274-278`, and `scripts/generate_estimation.py:550-554` **Vulnerability Type**: Spreadsheet formula injection **Risk Level**: Medium ### Vulnerable Code ```python def set_cell(ws, row, col, value, bold=False, align='left', fill=None): cell = ws.cell(row=row, column=col, value=value) cell.font = Font(bold=bold) cell.alignment = Alignment(horizontal=align, vertical='center') cell.border = BORDER_THIN if fill: cell.fill = fill return cell ``` Examples of requirement-derived values reaching this sink without sanitization include: ```python set_cell(ws, row, 1, module["name"]) set_cell(ws, row, 2, item["name"]) ``` ```python set_cell(ws, row, 1, module["name"]) set_cell(ws, row, 2, item["name"]) set_cell(ws, row, 3, item.get("desc", "")) set_cell(ws, row, 4, hours, align='center') set_cell(ws, row, 5, item.get("basis", f"Based on {sheet_name} standard")) ``` ```python set_cell(ws, row, 1, module["name"]) set_cell(ws, row, 2, item["name"]) set_cell(ws, row, 3, item.get("prerequisite", "-")) set_cell(ws, row, 4, item.get("coordination", "-")) set_cell(ws, row, 5, item.get("coord_target", "To be confirmed")) ``` ### Technical Analysis The Skill accepts user-supplied requirement descriptions or requirement documents and converts their content into module names, item names, descriptions, prerequisites, coordination details, and other workbook fields. The generic `set_cell()` function passes these strings directly to `openpyxl` without distinguishing plain text from formulas. A string beginning with `=` can be stored as an Excel formula. Depending on the spreadsheet application and its compatibility rules, values beginning with `+`, `-`, or `@` may also be interpreted as formulas. Leading whitespace, tabs, or control characte ...[truncated 2387 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Centralize spreadsheet-safe text handling in `set_cell()` so every workbook sheet receives consistent protection. 2. Treat all user-controlled or AI-derived descriptive fields as text rather than formulas. 3. Normalize leading control characters and inspect the first meaningful character for formula prefixes such as `=`, `+`, `-`, and `@`. 4. Prefix unsafe values with an apostrophe or otherwise encode them as literal text before assigning them to the cell. 5. Preserve numeric types only for fields that have passed strict numeric validation. 6. Add explicit length and character restrictions for names, descriptions, prerequisites, risk details, and coordination fields. 7. Add tests covering direct formula prefixes and bypass variants involving spaces, tabs, carriage returns, and line feeds. 8. Document that requirement documents are untrusted inputs and that generated workbooks must not contain executable formulas from those inputs. A centralized hardening pattern could be implemented as follows: ```python FORMULA_PREFIXES = ("=", "+", "-", "@") def spreadsheet_safe_value(value): if not isinstance(value, str): return value normalized = value.lstrip("\t\r\n") if normalized.startswith(FORMULA_PREFIXES): return "'" + value return value def set_cell(ws, row, col, value, bold=False, align="left", fill=None): safe_value = spreadsheet_safe_value(value) cell = ws.cell(row=row, column=col, value=safe_value) cell.font = Font(bold=bold) cell.alignment = Alignment(horizontal=align, vertical="center") cell.border = BORDER_THIN if fill: cell.fill = fill return cell ``` Where practical, use an allowlist for fields expected to contain only identifiers or short names. Security tests should then open the generated workbook with `openpyxl` using `data_only=False` and verify that attacker-controlled cells have `data_type == "s"` rather than formula type `f`. ]]>
