Artisan 安全性與維運
這個 bridge 會在 Chrome 內渲染可能不受信任的 HTML,外層由兩道獨立的網路屏障與一套嚴格的內容政策保護。Chrome 作業系統 sandbox 是另一道獨立、可選的控制,而且有明確限制。本頁說明這道邊界;並未主張這道邊界絕對安全。
概念總覽
標題為「概念總覽」的區段一次渲染就是一次在伺服器端執行的請求:應用程式把 HTML 交給瀏覽器引擎,而該引擎在預設情況下可以主動抓取資源。由不受信任輸入驅動的對外抓取,就是伺服器端請求偽造:CWE-918 將其定義為伺服器在未充分確認請求是否送往預期目的地的情況下,取得指定 URL 的內容。SSRF(CWE-918)屬於 CWE Top 25 弱點。OWASP ASVS 要求伺服器元件的對外請求必須受到控制,而非隱含放行。OWASP SSRF Prevention Cheat Sheet 將「在網路層拒絕呼叫任意目的地」視為強控制。下方預設拒絕的網路態勢,就是這個 bridge 對該要求的回應。NIST SP 800-53 SC-7 所描述的「全部拒絕、例外放行」邊界原則,正是這個 bridge 在傳輸層採用的原則。
資料落地與 PII 緩解措施
標題為「資料落地與 PII 緩解措施」的區段傳給 bridge 的 HTML 只會在行程內,以及本機 Chrome 實例內處理。bridge 本身不發出任何對外網路呼叫,並會阻擋 Chrome 發出任何對外呼叫(見下方網路模型),因此輸入內容不會經由 renderer 離開主機。輸入中的 PII 會被渲染進你產出的 PDF —— 請以與輸入相同的落地控制處理輸出。bridge 不會把輸入或輸出寫入磁碟;持久化是呼叫端的責任。
安全遙測與記錄清洗
標題為「安全遙測與記錄清洗」的區段ChromeHtmlRenderer 與 BrowserPool 接受選用的 PSR-3 LoggerInterface。這個 bridge 只記錄維運中繼資料:輸入位元組長度、目標寬度與高度、輸出位元組長度、量測到的內容高度、以設定的執行檔路徑啟動瀏覽器、含渲染次數的重啟通知,以及關閉事件。它不會記錄 HTML 內容、渲染後的位元組,或抽取出的文字。這符合 NIST SP 800-92 的指引:記錄維運事件,同時讓敏感負載不進入記錄。執行檔路徑會被記錄;請把它視為非敏感的部署中繼資料。記錄呼叫的格式由 tests/Unit/Artisan/ChromeHtmlRendererTest.php::renderLogsDebugWithSizeWidthHeightAndPdfSize 與 tests/Unit/Artisan/BrowserPoolTest.php::getBrowserLogsInfoOnLaunchWithBinaryPath 斷言。
網路隔離模型(縱深防禦)
標題為「網路隔離模型(縱深防禦)」的區段這個 bridge 套用兩道獨立屏障;即使其中一道被繞過,也不會曝露主機:
-
**Content-Security-Policy。**每次渲染都會由
ChromeSecurityPolicy::wrapHtml()包進一份包含以下內容的文件:default-src 'none'; style-src 'unsafe-inline'; img-src data:;base-uri 'none'; form-action 'none'; frame-ancestors 'none';navigate-to 'none';default-src 'none'拒絕所有資源來源。img-src data:只允許內嵌影像。navigate-to 'none'阻擋用戶端導覽。style-src 'unsafe-inline'是為了讓 ChromeprintToPDF能套用內嵌樣式所需的唯一放寬之處。這在src/Artisan/ChromeSecurityPolicy.php中驗證,並由ChromeSecurityPolicyTest::wrapHtmlIncludesNavigationCspDirectives斷言。 -
**CDP 傳輸阻擋。**載入內容之前,
ChromeHtmlRenderer會先送出Network.enable,接著以Network.setBlockedURLs搭配模式['*'],不論 CSP 為何,都會在 Chrome DevTools Protocol 傳輸層阻擋每一個子資源 URL。這在src/Artisan/ChromeHtmlRenderer::blockAllNetworkRequests()中驗證,並由ChromeHtmlRendererTest::renderAutoFitsHeightAndBlocksNetworkRequests斷言(它會檢查確切的 CDP 方法順序與['urls' => ['*']]參數)。這正是 OWASP SSRF 指引建議作為最強控制的網路層阻擋,也是與 NIST SP 800-53 SC-7 一致的傳輸層全部拒絕。
結果:輸入中的遠端 <img>、樣式表、字型、指令稿或 iframe 的 URL 都不會載入。這個 bridge 沒有實作網域允許清單或私有 IP 過濾,因為不需要 —— 它根本不允許任何對外的子資源抓取。
漂移備註:
nextpdf/core在writeHtmlChrome()的 docblock 寫著 Chrome「會抓取外部資源」,並建議設定一套政策來「封鎖私有 IP 範圍並限制允許的網域」。這描述的是可設定允許清單的模型。實際出貨的 ArtisanChromeSecurityPolicy並未提供允許清單,而是無條件封鎖所有子資源請求。以程式碼為準,而不是 core 的 docblock。此漂移已記錄給 core 文件團隊。
輸入驗證(進入 Chrome 之前)
標題為「輸入驗證(進入 Chrome 之前)」的區段ChromeSecurityPolicy::validate() 會在接觸 Chrome 之前執行,並拒絕:
| 檢查項 | 上限 | 理由 |
|---|---|---|
| HTML 大小 | > maxHtmlSize(預設 5 MB) | 資源耗盡的界限(CWE Top 25 失控資源消耗) |
| Base64 資料 URI | 擷取群組 >= 13_000_000 位元組 | 解壓縮炸彈的界限 |
<meta http-equiv="refresh"> | 任何形式(不分大小寫,single/double 引號) | 阻擋對內部端點的用戶端重新導向 —— 一種 SSRF 導覽向量 |
封鎖 meta-refresh 是明確的 SSRF 防護強化:少了它,攻擊者的 HTML 就可能在 printToPDF 之前,把 Chrome 重新導向到雲端中繼資料端點。邊界行為由 ChromeSecurityPolicyTest 全面斷言(validateThrowsOnOversizedHtml、validateRejectsMetaRefreshRedirect、validateRejectsMetaRefreshCaseInsensitive、validateRejectsMetaRefreshWithSingleQuotes、validateRejectsOversizedBase64DataUri、validateRejectsBase64DataUriAtExactThreshold)。
此外,ChromeSecurityPolicy::wrapHtml() 會在注入前,將 </style> 從 defaultCss 中移除,以防止內容從樣式區塊突圍進入指令稿情境(由 ChromeSecurityPolicyTest::wrapHtmlStripsStyleClosingTagsFromDefaultCss 斷言)。
Chrome sandbox 邊界 —— 明確說明
標題為「Chrome sandbox 邊界 —— 明確說明」的區段Chrome 作業系統 sandbox 是與上述網路屏障不同的另一道控制,而這個 bridge 並不保證它一定生效。
- 預設情況下
noSandbox為false,因此 Chrome 會在啟動時啟用自身的 sandbox。這個 bridge 並未實作 sandbox;它依賴 Chrome 執行檔的 sandbox,而後者取決於主機核心支援。 - 設定
noSandbox: true會以--no-sandbox啟動 Chrome。這會移除 Chrome 的行程隔離 sandbox。它是為了 sandbox 無法初始化的容器而提供的。這會實質降低隔離程度:renderer 一旦遭入侵,就不再受 Chrome 的 sandbox 圍堵。 - 無論是否啟用 sandbox,這個 bridge 的網路屏障(CSP 加 CDP 阻擋)都會維持生效,但它們不能替代行程隔離。OWASP ASVS 的最小權限指引適用於此:以非 root 使用者執行 Chrome、置於受限容器中、只在無法避免時才使用
noSandbox,並把--no-sandbox部署視為對輸入有更高信任要求。
本文件並未主張這個 bridge「預設安全」、「防竄改」,也未主張停用 sandbox 是安全的。它說明的是現有控制,以及這些控制止步於何處。如何佈建具備 sandbox 能力的容器,請見 /integrations/artisan/chrome-renderer-setup/ 頁面。
失敗模式
標題為「失敗模式」的區段以下列舉自 src/Artisan/Exception/ 與 render/transport 程式碼:
| 條件 | 呈現為 | 來源 |
|---|---|---|
chrome-php/chrome 函式庫缺漏 | ChromeNotAvailableException(附安裝指令) | BrowserPool::getBrowser() |
HTML 超過 maxHtmlSize | RuntimeException(訊息「exceeds maximum allowed size」,即超過允許的最大大小) | ChromeSecurityPolicy::validate() |
| 過大的 base64 資料 URI | RuntimeException(訊息「oversized base64 data URI」,即 base64 資料 URI 過大) | ChromeSecurityPolicy::validate() |
| 被禁止的 meta-refresh | RuntimeException(訊息「forbidden meta refresh redirect」,即禁止的 meta-refresh 重新導向) | ChromeSecurityPolicy::validate() |
| Chrome 啟動/逾時/當機 | ChromeRenderException(包裹起因) | ChromeHtmlRenderer::render() |
| Chrome 回傳空白 PDF | ChromeRenderException(訊息「returned empty data」,即回傳了空白資料) | ChromeHtmlRenderer::render() |
| 頁面沒有內容串流 | PdfParseException | PageImporter::import() |
在渲染流程內部引發的 ChromeRenderException 會原樣重新拋出。任何其他 Throwable 會被包裹成 ChromeRenderException,並保留前一個例外(由 ChromeHtmlRendererTest::renderRethrowsChromeRenderExceptionWithoutWrapping 與 ::renderWrapsUnexpectedThrowablesWithChromeRenderException 斷言)。即使失敗,Chrome 頁面也一律會在 finally 區塊中關閉。
資源限制
標題為「資源限制」的區段- 輸入大小:
maxHtmlSize(預設 5 MB)以及 13 MB 的 base64 資料 URI 上限。 - 時間:
renderTimeout秒數同時界定內容載入與 CDP 同步呼叫的時間。CDP 控制指令使用固定的 5 秒逾時。 - 行程:
BrowserPool每 100 次渲染就重啟 Chrome 以限制記憶體成長,並在close()/銷毀時關閉行程。
這些是界限,而不是配額。對於任何曝露於不受信任輸入的路徑,仍建議搭配主機層級的資源限制(cgroup、ulimit、請求預算),這與 CWE Top 25 的資源消耗指引一致。
可觀測性掛鉤
標題為「可觀測性掛鉤」的區段注入 PSR-3 logger 以擷取:渲染開始(大小、寬度、高度)、渲染完成(輸出大小、內容高度)、瀏覽器啟動(執行檔路徑)、瀏覽器重啟(渲染次數)、瀏覽器關閉(渲染次數)。這些是唯一發出的事件,而且不夾帶任何負載內容。請用它們建立延遲 SLO 與重啟率告警。
符合性
標題為「符合性」的區段| 主張 | 參考依據 | clause_id(子句 ID) | reference_id(參考 ID) |
|---|---|---|---|
| 伺服器元件的對外請求必須受到控制 | OWASP ASVS 5.0 | §(SSRF/對外控制) | |
| SSRF = 伺服器在未驗證目的地的情況下取得指定 URL | CWE Top 25 2025 標準(CWE-918) | cwe_top25_2025#x28.x2.p2 | |
| SSRF(CWE-918)是一項 CWE Top 25 弱點 | CWE Top 25 2025 標準 | cwe_top25_2025#x1.p73 | |
| 失控資源消耗是一項 CWE Top 25 弱點 | CWE Top 25 2025 標準(CWE-400) | cwe_top25_2025#x19.x2.p2 | |
| 預設拒絕的邊界保護(例外放行) | NIST SP 800-53 Rev 5 SC-7 標準 | SC-7 | |
| 在網路層拒絕呼叫任意目的地是強的 SSRF 控制 | OWASP Cheat Sheet Series(SSRF Prevention §網路層) | owasp_cheatsheet_series#x132.x2 | |
| 保護抓取 URL 的元件以抵禦 SSRF | OWASP Cheat Sheet Series 指引 | §(SSRF 防護、抓取 URL 工具) | |
| 隔離不受信任內容的渲染、最小權限 | OWASP ASVS 5.0 | §(sandbox/最小權限) | |
| 記錄維運事件;讓負載不進入記錄 | NIST SP 800-92 | §(記錄內容指引) |
這些引用是透過 NextPDF 符合性引擎取得(語料庫資訊清單 1d05b7c4…d790b6);條文內容均為改寫,絕不逐字引用。
威脅模型
標題為「威脅模型」的區段| 威脅 | 控制 | 殘餘風險 |
|---|---|---|
| 經由遠端子資源的 SSRF | CSP default-src 'none' 加 CDP setBlockedURLs('*') | 同時繞過兩道屏障的 Chrome 引擎漏洞(縱深防禦只能降低風險,無法消除風險) |
| 經由 meta-refresh 導覽的 SSRF | 進入 Chrome 前的驗證會拒絕該標籤 | 模式未涵蓋到的新導覽向量 |
| 資源耗盡 | 輸入大小加 base64 上限加逾時加每 100 次渲染重啟 | 沒有每主機配額;請搭配 cgroup/ulimit |
| renderer 行程遭入侵 | Chrome sandbox 啟用時 | noSandbox: true 會完全移除這道控制 |
| 樣式突圍/注入 | </style> 移除自 defaultCss;CSP 阻擋指令稿 | 透過未來某個未被移除的向量進行注入 |
FIPS 模式行為
標題為「FIPS 模式行為」的區段這個 bridge 不執行任何密碼學運算。它透過 Chrome 產生 PDF 位元組並加以內嵌。簽章、加密與 FIPS 模式行為屬於 core/Premium 的範疇,不受 Artisan 影響。
另請參閱
標題為「另請參閱」的區段- 組態:/integrations/artisan/configuration/
- Chrome renderer(渲染器)設定:/integrations/artisan/chrome-renderer-setup/
- 疑難排解:/integrations/artisan/troubleshooting/
- 生產環境使用:/integrations/artisan/production-usage/
- 總覽:/integrations/artisan/overview/