WeasyPrint CVE-2026-49452 處置紀錄(升版 69.0 + not-affected 論證)

  • 日期:2026-07-25
  • 處置人:Claude(資安掃描 arc 2607,Phase 2 執行)
  • 結論:升版 weasyprint 68.1 → 69.0(上游修復版),trivy 重掃 CVE 消失。本專案在升版前即為 not affected(漏洞碼不在執行路徑上),升版屬縱深防禦 + 消除掃描噪音。

1. CVE 摘要與觸發條件

  • CVE-2026-49452 / GHSA-jhhc-3hcp-qhm5:WeasyPrint 在啟用 presentational_hints=True 時,會解析 HTML presentational 屬性(如 bgcolorwidth 等);受影響版本(<=68.1)在該路徑下對使用者可控 HTML 的處理存在漏洞。
  • 觸發需要兩個條件同時成立
    1. 呼叫端顯式開啟 presentational_hints=True(預設為 False);
    2. 渲染的 HTML 內容含攻擊者可控的 presentational 屬性。
  • 修復版:69.0

2. 本專案判定:not affected(升版前即成立)

Justification(CSAF/VEX 用語):vulnerable_code_not_in_execute_path

盤點證據(Phase 1 唯讀盤點確立,本 phase 複核):

  1. 全 repo 零處 presentational_hints — grep 全案(含 jedi-* 套件引用面)無任何開啟該選項的呼叫。
  2. 唯一 WeasyPrint 呼叫點只有一處,且全預設參數
    • api/project_summary_report/routes/project_summary_report_route.py:148
    • weasyprint.HTML(string=html).write_pdf() — 未傳 presentational_hints,維持預設 False,漏洞碼路徑根本不會執行。
  3. 輸入面已過 bleach 白名單(同檔 _SUMMARY_ALLOWED_TAGS / _SUMMARY_ALLOWED_ATTRIBUTES):
    • 允許屬性僅 a[href]img[src, alt]無 style、無 on 事件、無任何 presentational 屬性*(bgcolor / width / align 等全數被 strip)。
    • 即使未來有人誤開 presentational_hints,可控輸入面也已被白名單掐斷,屬第二層防線。

3. 處置內容

項目 內容
版本約束 pyproject.toml weasyprint (>=68.0,<69.0)(>=69.0,<70.0)
實裝版本 68.1 → 69.0poetry update weasyprint 單套件更新,lock 同步)
69.0 breaking changes srgb profile 改 output_intent、CLI flag 改名——本專案皆未使用,不受影響(Phase 1 確認)
程式碼 零異動(不動 template、不動 route;SSRF img[src] 與 Google Fonts 外連另案 CM-893/894)

4. trivy「FixedVersion: null」誤判說明

升版前 trivy 將此 CVE 列為 no-fix(FixedVersion 為 null),這是 GHSA-jhhc-3hcp-qhm5 上游 advisory 的 metadata 缺漏——GHSA 標了受影響範圍 <=68.1 但沒回填 patched version 欄位,trivy 忠實轉載成「無修復版」。實際上 69.0 即修復版且不在受影響範圍內。

升版後重掃驗證(2026-07-25)trivy fs . --scanners vuln(trivy 依 GHSA 的 <=68.1 range 判定 69.0 不受影響):

  • weasyprint 相關 vuln:0 條,CVE-2026-49452 已從結果消失。
  • 全 repo Python 依賴 vuln 總數:0 條(BE MEDIUM 清零狀態維持)。
  • 因此不需要 .trivyignore 條目——原 Phase 規劃的 ignore fallback 僅在「升版後 trivy 仍誤報」時才需要,實測未發生。

5. 升版前後 PDF 視覺比對(回歸驗證)

比對方法:以實際 template(project_summary_report_template.html)+ jinja2 手動 render 假 report(中文標題 + 富文本 summary:<b>/<u>/<ul>/<ol>/<table>/<h2>/<h3>/<blockquote>/<pre> + 20 段跨頁長中文段落),分別在 68.1 / 69.0 下 HTML(string=html).write_pdf(),再做三層比對。腳本與產物在 /private/tmp/weasyprint-upgrade/(不進 repo)。

比對層 方法 結果
頁數 pypdf 兩版皆 4 頁,一致
文字層 pypdf extract_text 全文比對 完全一致(4100 字元,逐字相同)
像素層 pdftoppm 100dpi 轉圖 + PIL 逐頁 pixel diff 4 頁全部 0 px 差異(diff bbox=None)

另目視抽查渲染頁面:中文(Noto Sans TC fallback)、表格框線、清單、粗體/底線均正常,無豆腐字、無表格斷裂、無分頁錯亂。視覺回歸:通過。

6. 部署提醒

  • 本次僅依賴升版,BE 執行中服務不需即刻重啟(下次啟動生效)。
  • 部署機(STG / POC / 客戶環境)需在拉取本 commit 後執行 poetry update weasyprint(或 poetry install 同步 lock),否則實裝仍為 68.1、掃描會繼續報此 CVE。