Coverage for src/local_deep_research/document_loaders/openpyxl_hardening.py: 90%

16 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-22 21:20 +0000

1"""Stop openpyxl from reading chartsheet drawings (images and charts). 

2 

3``.xlsx`` text extraction goes unstructured ``partition_xlsx`` -> 

4``pandas.read_excel`` -> ``openpyxl.load_workbook(read_only=True)``. 

5Even in read-only mode openpyxl loads every chartsheet's drawing via 

6``openpyxl.reader.drawings.find_images``, which calls 

7``archive.read(target)`` once **per image reference** and keeps every 

8copy (and parses a chart part once per chart reference). One image 

9entry referenced N times therefore costs N times its size: a ~100 KB 

10upload can pin gigabytes, which no per-entry ceiling can bound. 

11 

12Images and charts contribute no text, and this application uses 

13openpyxl only to extract text, so the reader's drawing hook is replaced 

14with one that returns nothing. That removes the per-reference reads at 

15their source instead of trying to count references in attacker-written 

16XML. (Worksheet drawings are only read outside read-only mode, which 

17this path never uses.) 

18 

19The patch is **process-wide and permanent**: once the first ``.xlsx`` 

20upload is processed, every openpyxl workbook loaded anywhere in this 

21process has no images or charts. Nothing else in the application reads 

22workbooks with openpyxl; anything added later that needs drawings must 

23load them some other way. 

24""" 

25 

26from __future__ import annotations 

27 

28from loguru import logger 

29 

30 

31def _no_drawings(archive, path): # noqa: ARG001 - signature of find_images 

32 """Stand-in for ``openpyxl.reader.drawings.find_images``.""" 

33 return [], [] 

34 

35 

36def disable_openpyxl_drawing_reads() -> bool: 

37 """Replace openpyxl's drawing reader with a no-op (idempotent). 

38 

39 Returns True when the hook is in place. Returns False, with a 

40 warning, when openpyxl is missing or no longer exposes the hook. 

41 The upload caller refuses .xlsx files in that case. 

42 """ 

43 try: 

44 import openpyxl.reader.excel as excel_reader 

45 except ImportError: 

46 return False 

47 # Checked by identity rather than a flag, so a reload of openpyxl 

48 # (which restores the original hook) is patched again. 

49 if getattr(excel_reader, "find_images", None) is _no_drawings: 

50 return True 

51 if not hasattr(excel_reader, "find_images"): 

52 logger.warning( 

53 "openpyxl no longer exposes reader.excel.find_images; " 

54 ".xlsx drawings are read on load again" 

55 ) 

56 return False 

57 excel_reader.find_images = _no_drawings 

58 return True