C1+C3 review findings: streaming decompression-bomb guard + stored-XSS stripping
Test / Tests (Python 3.12) (push) Canceled after 0s

C1 (decompression bomb): wire the shared common._copy_stream_with_cap into both
zip-extraction paths. app.py._safe_extract_zip now streams each EPUB member with
a WRITTEN-byte counter (app.py:456) instead of an unbounded copyfileobj, so a
member that inflates a small header into a huge stream is stopped mid-write.
pandoc_converter._validate_office_zip now streams every member's decompressed
bytes through the same guard (pandoc_converter.py:134) before pandoc reads them.

C3 (stored XSS): common.html_to_pdf_bytes now strips stored-XSS vectors via
common._strip_scripts (common.py:320) BEFORE WeasyPrint renders, so every
non-pandoc render path (html, svg, epub, md, csv, xml) neutralizes <script>/
<iframe> content, on* handlers and javascript:/vbscript: URLs. Regression tests
added to tests/test_review_findings_20260903.py (C1 unit+e2e, C3 unit+e2e).
This commit is contained in:
sirdave73
2026-09-04 02:25:07 -04:00
parent 51d39fead1
commit 72b5f2f0dd
4 changed files with 227 additions and 20 deletions
+10 -1
View File
@@ -407,6 +407,10 @@ def _safe_extract_zip(epub_path: Path, extract_dir: Path) -> None:
Also enforces the zip-bomb cap: total uncompressed size must stay within
max(8x upload size, ZIP_BOMB_MIN_CAP) or the job fails with a clear error.
"""
# Lazy import: app imports the converters package at module load (app.py:37),
# but the common submodule is pulled lazily here to avoid any import-cycle
# concern. Provides the C1 streaming decompression-bomb guard.
from converters import common
upload_size = epub_path.stat().st_size
cap = max(8 * upload_size, ZIP_BOMB_MIN_CAP)
try:
@@ -444,7 +448,12 @@ def _safe_extract_zip(epub_path: Path, extract_dir: Path) -> None:
continue
target.parent.mkdir(parents=True, exist_ok=True)
with zf.open(info) as src, open(target, "wb") as dst:
shutil.copyfileobj(src, dst)
# C1 (real enforcement): stream-copy counting WRITTEN bytes so a
# member that inflates a small header into a huge stream is stopped
# mid-write. A header-only check (lines 418-427) cannot see a member
# whose declared size is understated; this counts actual bytes and
# mirrors the shared guard wired into pandoc._validate_office_zip.
common._copy_stream_with_cap(src, dst, cap)
# --------------------------------------------------------------------------