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
+11
View File
@@ -121,6 +121,17 @@ def _validate_office_zip(path: Path) -> None:
raise ValueError(
f"zip-bomb guard: uncompressed size {total} exceeds cap {cap}"
)
# C1 (real enforcement): stream every member's DECOMPRESSED bytes through the
# shared counter (dst=None -> count-only) so a container that inflates a small
# header into a huge stream is stopped mid-read, before pandoc ever opens it.
# The header check above is defense-in-depth; this counts actual bytes and
# mirrors _safe_extract_zip's streaming guard.
with zipfile.ZipFile(path) as zf:
for info in zf.infolist():
if info.is_dir():
continue
with zf.open(info) as member:
common._copy_stream_with_cap(member, None, cap)
root = path.parent.resolve()
for info in infos:
name = info.filename