Artisan のセキュリティと運用
ブリッジは、信頼できない可能性がある HTML を、2 つの独立したネットワーク障壁と厳格なコンテンツポリシーの背後に置いた Chrome 内でレンダリングします。Chrome OS サンドボックスは、制限事項が明示された別個のオプション制御です。このページでは境界を文書化しますが、その境界が絶対的であると主張するものではありません。
概念の概要
「概念の概要」という見出しのセクションレンダリングは、サーバーサイドでリクエストを実行する処理です。アプリケーションは HTML をブラウザーエンジンに渡し、そのエンジンはデフォルトでリソースを取得できます。信頼できない入力に起因するアウトバウンド取得は、サーバーサイドリクエストフォージェリです。CWE-918 はこれを、リクエストが想定された宛先に向かうことを十分に保証しないまま、指定された URL の内容を取得するサーバーとして定義しています。SSRF(CWE-918)は CWE Top 25 の脆弱性です。OWASP ASVS は、サーバーコンポーネントからのアウトバウンドリクエストを暗黙的に許可せず、制御することを要求しています。OWASP SSRF Prevention Cheat Sheet は、任意の宛先への呼び出しをネットワーク層で拒否することを強力な制御として扱います。以下のデフォルト拒否のネットワーク姿勢は、その要件に対するブリッジの対応です。NIST SP 800-53 SC-7 は、ブリッジがトランスポート層で適用するものと同じ「すべて拒否、例外による許可」の境界原則を記述しています。
データレジデンシーと PII の緩和策
「データレジデンシーと PII の緩和策」という見出しのセクションブリッジに渡された HTML はすべて、プロセス内およびローカルの Chrome インスタンス内で処理されます。ブリッジは独自のアウトバウンドネットワーク呼び出しを行わず、Chrome による呼び出しもブロックするため(以下のネットワークモデルを参照)、入力コンテンツがレンダラーを通じてホスト外へ出ることはありません。入力内の PII は、出力 PDF にレンダリングされます。出力は入力と同じレジデンシー制御で扱ってください。ブリッジは入力や出力をディスクに永続化しません。永続化は呼び出し側の責任です。
安全なテレメトリとログのスクラビング
「安全なテレメトリとログのスクラビング」という見出しのセクションChromeHtmlRenderer と BrowserPool は、オプションの PSR-3 LoggerInterface を受け取ります。ブリッジが記録するのは運用メタデータだけです。具体的には、入力のバイト長、対象の幅と高さ、出力のバイト長、測定されたコンテンツの高さ、構成されたバイナリパスでのブラウザー起動、レンダリング回数を含む再起動通知、クローズイベントです。HTML コンテンツ、レンダリングされたバイト、抽出されたテキストは 記録しません。これは、運用イベントを記録しつつ機密ペイロードをログから排除するという NIST SP 800-92 のガイダンスに一致します。バイナリパスは記録されます。機密性のないデプロイメントメタデータとして扱ってください。ログ呼び出しの形状は、tests/Unit/Artisan/ChromeHtmlRendererTest.php::renderLogsDebugWithSizeWidthHeightAndPdfSize と tests/Unit/Artisan/BrowserPoolTest.php::getBrowserLogsInfoOnLaunchWithBinaryPath によってアサートされます。
ネットワーク分離モデル(多層防御)
「ネットワーク分離モデル(多層防御)」という見出しのセクションブリッジは 2 つの独立した障壁を適用するため、一方が回避されてもホストが露出することはありません。
-
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'は、Chrome のprintToPDFがインラインスタイルを適用するために必要な唯一の緩和です。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 は読み込まれません。ブリッジはドメイン許可リストやプライベート IP フィルターを実装していません。必要がないためです。アウトバウンドのサブリソース取得を一切許可しません。
ドリフトに関する注記:
nextpdf/coreのwriteHtmlChrome()の docblock は、Chrome が「外部リソースを取得する」と述べ、「プライベート IP 範囲をブロックし、許可ドメインを制限する」ポリシーの構成を推奨しています。これは構成可能な許可リストモデルを記述したものです。出荷される Artisan のChromeSecurityPolicyは許可リストを公開せず、代わりに すべての サブリソースリクエストを無条件にブロックします。正とするべきなのはコアの docblock ではなくコードです。このドリフトはコアドキュメントチーム向けに記録されています。
入力の検証(Chrome 前)
「入力の検証(Chrome 前)」という見出しのセクションChromeSecurityPolicy::validate() は Chrome に接続する前に実行され、以下を拒否します。
| チェック | 制限 | 根拠 |
|---|---|---|
| HTML サイズ | > maxHtmlSize(デフォルト 5 MB) | リソース枯渇の上限(CWE Top 25 の制御されないリソース消費) |
| Base64 データ URI | キャプチャグループ >= 13_000_000 バイト | 解凍爆弾の上限 |
<meta http-equiv="refresh"> | 任意(大文字小文字を区別しない、single/double 引用符) | 内部エンドポイントへのクライアントサイドリダイレクトのブロック — SSRF のナビゲーションベクトル |
メタリフレッシュのブロックは、明示的な SSRF ハードニングです。これがなければ、攻撃者の HTML によって、printToPDF の前に Chrome がクラウドメタデータエンドポイントへリダイレクトされる可能性があります。境界動作は、ChromeSecurityPolicyTest(validateThrowsOnOversizedHtml、validateRejectsMetaRefreshRedirect、validateRejectsMetaRefreshCaseInsensitive、validateRejectsMetaRefreshWithSingleQuotes、validateRejectsOversizedBase64DataUri、validateRejectsBase64DataUriAtExactThreshold)全体でアサートされます。
さらに、ChromeSecurityPolicy::wrapHtml() は、スタイルブロックからスクリプトコンテキストへのブレイクアウトを防ぐために、</style> を defaultCss から取り除いてから注入します(ChromeSecurityPolicyTest::wrapHtmlStripsStyleClosingTagsFromDefaultCss によってアサートされます)。
Chrome サンドボックス境界 — 明示的な記述
「Chrome サンドボックス境界 — 明示的な記述」という見出しのセクションChrome OS サンドボックスは、上記のネットワーク障壁とは 別個 の制御であり、ブリッジが保証するものではありません。
- デフォルトでは
noSandboxはfalseのため、Chrome は独自のサンドボックスを有効にして起動します。ブリッジ自体はサンドボックスを実装せず、Chrome バイナリのサンドボックスに依存します。このサンドボックスはホストカーネルのサポートに依存します。 - オプション
noSandbox: trueを設定すると、Chrome は--no-sandboxで起動します。これは Chrome のプロセス分離サンドボックスを 除去 します。サンドボックスを初期化できないコンテナー向けに提供されている設定です。これは分離の実質的な低下です。レンダラーが侵害された場合、もはや Chrome のサンドボックスでは封じ込められなくなります。 - ブリッジのネットワーク障壁(CSP + CDP ブロック)は、サンドボックスの有効・無効にかかわらず引き続き有効ですが、プロセス分離の代替にはなりません。OWASP ASVS の最小権限ガイダンスが適用されます。Chrome を非 root ユーザーとして、制約されたコンテナー内で実行し、
noSandboxは避けられない場合にのみ使用し、--no-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() |
| 禁止されたメタリフレッシュ | RuntimeException(「forbidden meta refresh redirect」、禁止されたメタリフレッシュリダイレクト) | 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()/ デストラクション時にプロセスをクローズします。
これらは上限であり、クォータではありません。CWE Top 25 のリソース消費ガイダンスに従い、信頼できない入力に公開されるパスには、ホストレベルのリソース制限(cgroup、ulimit、リクエスト予算)を引き続き推奨します。
オブザーバビリティフック
「オブザーバビリティフック」という見出しのセクションPSR-3 ロガーを注入すると、次をキャプチャできます。レンダリング開始(サイズ、幅、高さ)、レンダリング完了(出力サイズ、コンテンツの高さ)、ブラウザー起動(バイナリパス)、ブラウザー再起動(レンダリング回数)、ブラウザークローズ(レンダリング回数)。発行されるイベントはこれらだけであり、ペイロードコンテンツは一切含まれません。レイテンシ SLO や再起動率のアラートに使用してください。
| 主張 | 参照 | 条項の 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 | §(サンドボックス / 最小権限) | |
| 運用イベントの記録と、ログからのペイロード排除 | NIST SP 800-92 | §(ログコンテンツのガイダンス) |
引用は NextPDF コンプライアンスエンジン(コーパスマニフェスト 1d05b7c4…d790b6)を介して取得されました。条項テキストは言い換えであり、引用ではありません。
脅威モデル
「脅威モデル」という見出しのセクション| 脅威 | 制御 | 残存リスク |
|---|---|---|
| リモートサブリソースを介した SSRF | CSP default-src 'none' + CDP setBlockedURLs('*') | 両方の障壁を回避する Chrome エンジンのバグ(多層防御はリスクを下げるが、排除はしない) |
| メタリフレッシュナビゲーションを介した SSRF | Chrome 前の検証がタグを拒否する | パターンに一致しない新たなナビゲーションベクトル |
| リソース枯渇 | 入力サイズ + base64 上限 + タイムアウト + 100 レンダリングごとの再起動 | ホストごとのクォータなし。cgroup/ulimit との併用 |
| レンダラープロセスの侵害 | Chrome サンドボックス (有効な場合) | noSandbox: true はこの制御を完全に除去する |
| スタイルのブレイクアウト / 注入 | </style> の defaultCss 内での除去。CSP がスクリプトをブロックする | 将来の未除去ベクトルを介した注入 |
FIPS モードの動作
「FIPS モードの動作」という見出しのセクションブリッジは暗号化操作を一切行いません。Chrome を介して PDF バイトを生成し、それらを埋め込みます。署名、暗号化、FIPS モードの動作はコア/Premium の関心事であり、Artisan の影響を受けません。
- /integrations/artisan/configuration/(構成ガイド)
- /integrations/artisan/chrome-renderer-setup/(Chrome レンダラーのセットアップ)
- /integrations/artisan/troubleshooting/(トラブルシューティング)
- /integrations/artisan/production-usage/(本番環境での使用)
- /integrations/artisan/overview/(概要ページ)