mirror of
https://github.com/yogeshojha/rengine.git
synced 2026-10-20 23:31:31 +02:00
Audited every path an image can reach the renderer by, on the principle that uploads are the only source. Four of the five were already closed, and the one that mattered most turned out to be closed for a reason worth recording: an SVG logo cannot beacon or read a file, because references made from inside the SVG go through the same fetcher, and WeasyPrint's SVG parser does not resolve XML entities. An <image href> pointing at a listener got zero hits; an XXE entity leaked nothing. So SVG stays. Rasterising a logo to avoid a risk that is already closed would cost real print quality for nothing. The gap was not a URL at all. WeasyPrint passes a PNG stream straight into the PDF, so the output stays small, but it still decodes the image: inside the 500 KB logo limit, a 182 KB file decodes to 64M pixels and cost a measured +594 MB of RSS, and Pillow's 89M default only warns below twice the limit. Across eight workers that is several gigabytes from a handful of small uploads. The render now caps decoded pixels, which takes that same payload to +0 MB, and a dropped image is surfaced as a report warning instead of vanishing. Two things this exposed about the previous commit. The fetcher was built on default_url_fetcher, deprecated in WeasyPrint 69; it is now a URLFetcher subclass pinned to the data and file protocols. And the first attempt at the pixel cap silently missed, because the edit was written against a function ruff had already reformatted: the guard existed, nothing called it, and the probe reported success by rendering the bomb. Measuring the test harness separately from the renderer is what caught it. cover_image was a validated field nothing rendered. It is wired now, uploaded and embedded like the logo, so it is no longer dead configuration. Verified after the rewrite: 11/11 on the fetcher matrix, a 124-page report with the uploaded typeface still embedded, and screenshots still embedding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017f2Mf2HGwFaTRnui4UxpRd