WeasyPrint 69.0 carries CVE-2026-55073, and the dependency audit refuses it #163
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Postulo/postulo#163
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observation
The
securityjob fails:Why now — and not because of a commit
The job audits the lock file against the advisory databases on every push and every Monday.
Nothing recent touched WeasyPrint: the advisory was published on 9 September 2026
(CVSS 6.2, medium) and the first run after it failed. Every earlier commit fails the same way
today, including both released versions — v0.2.0 and v0.2.1 lock WeasyPrint 69.0.
What the advisory is
From the GitLab advisory database:
two
write_pdf()parameters ignore the document'surl_fetcherand build a fresh default one.xmp_metadata=[url]— the URL is fetched and embedded in the PDF: an arbitrary local fileread when the path is attacker-influenced.
stylesheets=[url_or_path]— SSRF / arbitrary local-or-internal resource loading,transitive through
@importandurl().An application is affected when it runs WeasyPrint server-side, relies on a restrictive
url_fetcher, and forwards an attacker-influenced URL into either parameter. The70.0 release also stops rendering EPS images,
and recommends upgrading for anybody who embeds untrusted images.
Whether Postulo is exposed
Not through either channel described.
documents/pdf.pycallsHTML(string=html).write_pdf()— neitherstylesheetsnorxmp_metadata— and nothingWeasyPrint renders here (the CV, portfolio and letter themes,
report_print.html) contains an<img>or aurl().So this is not an emergency, but the gate is right to fail.
--strictexists so that aknown-vulnerable dependency is not shipped, and 0.3.0 should not be released on a lock the
audit refuses.
What fixing it is
The constraint is
weasyprint>=69, so nothing holds it back.uv lock --upgrade-package weasyprint --dry-runresolves exactly one change:The upgrade is not covered by the tests, and the documents are worth looking at. WeasyPrint
bumps its major version every release, and the suite renders every PDF through a fake backend —
it tests which backend is chosen, never what WeasyPrint draws. It also cannot run WeasyPrint on
Windows, where Pango is missing. So the check is: render a CV, a portfolio, a letter and a report
on Linux (in the image, where the libraries are) and read them.
Worth being careful about
scripts/scan-image.sh(#156) gates on fixablefindings, and this one has just become fixable, so the
imageworkflow fails on it too untilthe lock moves. Same fix.
release is a release decision rather than this issue's.
url_fetcherat all. That is safetoday because every document comes from escaped templates and inlines its own CSS: the comment
on the Chromium backend promises that "the renderer never reaches the network or the disk", and
it is true by construction. A fetcher that refuses everything would make it true by
enforcement, which starts to matter once plugin themes (#132) put markup Postulo did not write
in front of the renderer.
Classification
Security. Blocks CI, and so the 0.3.0 release.
Done in
dbd55b2c. Thesecurityjob has passed since (runs 274 and 276), and the live test instance now runs WeasyPrint 70.0.The upgrade
The lock is at 70.0, the only change the resolver makes. The requirement is now
weasyprint>=70, so an installation from the wheel cannot resolve to the old one. As you established, Postulo passes neitherstylesheetsnorxmp_metadata, and a comment onrender()now says why that matters.The optional hardening: taken
URLFetcher(allowed_protocols={"data"}, allow_redirects=False):data:and nothing else. Both 69 and 70 have that argument, so no fetcher of our own was needed.page.route("**/*", abort)). Adata:address is not a request, so an embedded image still draws.tests/security/test_pdf_fetching.pyholds both, and each test was run against the unsafe version and failed. With WeasyPrint's default fetcher, the fetcher test read the test's secret file, and a stylesheet on the disk turned the page into a 100 mm square. Without the route, Chromium fetched both/sheet.cssand/pixel.pngfrom a local listener. The wiki's CV page tells theme authors the rule: a theme draws only what it carries."The upgrade is not covered by the tests": now it is
tests/test_pdf_render.pydraws every kind of document in every shipped theme, a document set right to left, and the report, with the real renderer. In CI's test job (run 276):tests/test_pdf_render.py .........andtests/security/test_pdf_fetching.py ...ss. The two skipped are the Chromium tests, since that job has no browser.Running them on Linux first found that they would have skipped on CI in silence. WeasyPrint 70 warns at import when HarfBuzz-Subset is missing, and says a later version will require it. The suite treats warnings as errors, so the import failed and the availability check said no. The tests now skip on Pango itself, so a warning fails them instead of hiding them.
libharfbuzz-subset0is in the image, in both CI jobs that install Pango, and in the README and wiki install commands, and the changelog tells operators who install without the image.Reading real documents, on the test instance
I seeded a demo account in a throwaway container on SQLite in
/tmp, so nothing touched the live data, and drew the same HTML with 69.0 and 70.0 through Postulo's own backend. There were nine documents: the CV and the portfolio in both themes, four letters (cover, motivation, follow-up, in both themes) and the report.Worth knowing
b6cfb8f7, and the instance was backed up first (compose, .env, and a verified database backup).