# 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 屬性（如 `bgcolor`、`width` 等）；受影響版本（<=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.0**（`poetry 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。
