コンテンツにスキップ

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 は、ブリッジがトランスポート層で適用するものと同じ「すべて拒否、例外による許可」の境界原則を記述しています。

ブリッジに渡された HTML はすべて、プロセス内およびローカルの Chrome インスタンス内で処理されます。ブリッジは独自のアウトバウンドネットワーク呼び出しを行わず、Chrome による呼び出しもブロックするため(以下のネットワークモデルを参照)、入力コンテンツがレンダラーを通じてホスト外へ出ることはありません。入力内の PII は、出力 PDF にレンダリングされます。出力は入力と同じレジデンシー制御で扱ってください。ブリッジは入力や出力をディスクに永続化しません。永続化は呼び出し側の責任です。

ChromeHtmlRendererBrowserPool は、オプションの PSR-3 LoggerInterface を受け取ります。ブリッジが記録するのは運用メタデータだけです。具体的には、入力のバイト長、対象の幅と高さ、出力のバイト長、測定されたコンテンツの高さ、構成されたバイナリパスでのブラウザー起動、レンダリング回数を含む再起動通知、クローズイベントです。HTML コンテンツ、レンダリングされたバイト、抽出されたテキストは 記録しません。これは、運用イベントを記録しつつ機密ペイロードをログから排除するという NIST SP 800-92 のガイダンスに一致します。バイナリパスは記録されます。機密性のないデプロイメントメタデータとして扱ってください。ログ呼び出しの形状は、tests/Unit/Artisan/ChromeHtmlRendererTest.php::renderLogsDebugWithSizeWidthHeightAndPdfSizetests/Unit/Artisan/BrowserPoolTest.php::getBrowserLogsInfoOnLaunchWithBinaryPath によってアサートされます。

ブリッジは 2 つの独立した障壁を適用するため、一方が回避されてもホストが露出することはありません。

  1. 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 によってアサートされます。

  2. CDP トランスポートブロック。 コンテンツが読み込まれる前に、ChromeHtmlRendererNetwork.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/corewriteHtmlChrome() の docblock は、Chrome が「外部リソースを取得する」と述べ、「プライベート IP 範囲をブロックし、許可ドメインを制限する」ポリシーの構成を推奨しています。これは構成可能な許可リストモデルを記述したものです。出荷される Artisan の ChromeSecurityPolicy は許可リストを公開せず、代わりに すべての サブリソースリクエストを無条件にブロックします。正とするべきなのはコアの docblock ではなくコードです。このドリフトはコアドキュメントチーム向けに記録されています。

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 がクラウドメタデータエンドポイントへリダイレクトされる可能性があります。境界動作は、ChromeSecurityPolicyTestvalidateThrowsOnOversizedHtmlvalidateRejectsMetaRefreshRedirectvalidateRejectsMetaRefreshCaseInsensitivevalidateRejectsMetaRefreshWithSingleQuotesvalidateRejectsOversizedBase64DataUrivalidateRejectsBase64DataUriAtExactThreshold)全体でアサートされます。

さらに、ChromeSecurityPolicy::wrapHtml() は、スタイルブロックからスクリプトコンテキストへのブレイクアウトを防ぐために、</style>defaultCss から取り除いてから注入します(ChromeSecurityPolicyTest::wrapHtmlStripsStyleClosingTagsFromDefaultCss によってアサートされます)。

Chrome OS サンドボックスは、上記のネットワーク障壁とは 別個 の制御であり、ブリッジが保証するものではありません。

  • デフォルトでは noSandboxfalse のため、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 データ URIRuntimeException(「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()
ページにコンテンツストリームなしPdfParseExceptionPageImporter::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)を介して取得されました。条項テキストは言い換えであり、引用ではありません。

脅威制御残存リスク
リモートサブリソースを介した SSRFCSP default-src 'none' + CDP setBlockedURLs('*')両方の障壁を回避する Chrome エンジンのバグ(多層防御はリスクを下げるが、排除はしない)
メタリフレッシュナビゲーションを介した SSRFChrome 前の検証がタグを拒否するパターンに一致しない新たなナビゲーションベクトル
リソース枯渇入力サイズ + base64 上限 + タイムアウト + 100 レンダリングごとの再起動ホストごとのクォータなし。cgroup/ulimit との併用
レンダラープロセスの侵害Chrome サンドボックス (有効な場合)noSandbox: true はこの制御を完全に除去する
スタイルのブレイクアウト / 注入</style>defaultCss 内での除去。CSP がスクリプトをブロックする将来の未除去ベクトルを介した注入

ブリッジは暗号化操作を一切行いません。Chrome を介して PDF バイトを生成し、それらを埋め込みます。署名、暗号化、FIPS モードの動作はコア/Premium の関心事であり、Artisan の影響を受けません。

  • /integrations/artisan/configuration/(構成ガイド)
  • /integrations/artisan/chrome-renderer-setup/(Chrome レンダラーのセットアップ)
  • /integrations/artisan/troubleshooting/(トラブルシューティング)
  • /integrations/artisan/production-usage/(本番環境での使用)
  • /integrations/artisan/overview/(概要ページ)