T09 · Insecure Skill Coding Practices
- Location
scripts/adapters/spreadsheet.py:34- Finding
Unbounded XLSX Decompression and Worksheet Materialization
- Content
View full analysis
Vulnerability Details
File Location:
scripts/adapters/spreadsheet.py:34-45, 53-54, 68-80, 204-229
Vulnerability Type: Resource exhaustion through malicious XLSX input
Risk Level: MediumComplete Code Snippet
python def extract_spreadsheet( path: str | Path, *, sheet: str, mapping: ColumnMapping | Mapping[str, object], ) -> ExtractedTable: if not isinstance(sheet, str) or not sheet or sheet != sheet.strip(): raise StructuredValidationError("sheet must be a nonempty exact worksheet name") column_mapping = coerce_column_mapping(mapping) source = read_stable_local_file(path, suffixes=(".xlsx",)) openpyxl = _load_openpyxl() structure = _worksheet_structure(source, sheet) formula_book = None cached_book = None try: formula_book = openpyxl.load_workbook( BytesIO(source), read_only=True, data_only=False, keep_links=False, keep_vba=False, ) cached_book = openpyxl.load_workbook( BytesIO(source), read_only=True, data_only=True, keep_links=False, keep_vba=False, ) if sheet not in formula_book.sheetnames or sheet not in cached_book.sheetnames: raise StructuredValidationError("selected worksheet does not exist") formula_sheet = formula_book[sheet] cached_sheet = cached_book[sheet] formula_rows = list(formula_sheet.iter_rows()) cached_rows = list(cached_sheet.iter_rows()) if not formula_rows: raise MappingError("selected worksheet has no explicit header row") headers = [_header_value(cell.value) for cell in formula_rows[0]] positions = resolve_headers(headers, column_mapping) width = len(headers) extracted: list[ExtractedRow] = [] explicit_rows = structure.row_cells max_rows = max(len(formula_rows), max(explicit_rows, default=1) ...[truncated 4474 chars]- Remediation
View remediation
Remediation Suggestions
-
Validate ZIP metadata before extraction
- Impose strict limits on archive member count.
- Limit each member’s declared uncompressed size.
- Limit aggregate uncompressed size.
- Reject excessive compression ratios.
- Reject duplicate or malformed member names.
-
Use bounded streaming
- Replace unbounded
archive.read(...)calls with streaming reads that stop at a configured byte ceiling. - Parse XML incrementally with bounded input rather than constructing unrestricted byte strings and complete XML trees.
- Replace unbounded
-
Limit worksheet complexity
- Reject workbooks exceeding a practical row, column, or nonempty-cell budget for the research workflow.
- Validate worksheet dimensions before iterating through cells.
- Apply limits substantially below Excel’s theoretical maximum when the expected documents are ordinary admissions tables.
-
Avoid full worksheet materialization
- Process rows incrementally instead of calling
list(formula_sheet.iter_rows())andlist(cached_sheet.iter_rows()). - Retain only the rows and columns required by the explicit mapping.
- Avoid opening and traversing the workbook twice where a bounded alternative can preserve the required formula and cached-value checks.
- Process rows incrementally instead of calling
-
Apply process-level resource isolation
- Parse external office documents in a subprocess with memory, CPU, wall-clock, and file-size limits.
- Terminate the parser cleanly when any budget is exceeded and return a controlled “source unreadable” result.
-
Add adversarial tests
- Test high-compression XLSX archives, oversized worksheet XML, excessive sparse dimensions, very large row counts, and excessive ZIP members.
- Verify that every case fails before significant memory allocation or prolonged CPU consumption.
-
