このブログ投稿では、最近導入された [Issues] タブを使用して、コンテンツ セキュリティ ポリシー(CSP)の問題をデバッグするための DevTools サポートの実装について説明します。
実装作業は、次の 2 つのインターンシップで行われました。 1. 最初のフェーズでは、一般的なレポート フレームワークを構築し、3 つの CSP 違反問題に関する問題メッセージを設計しました。2. 2 回目のリリースでは、Trusted Types のデバッグに特化した DevTools の機能とともに、Trusted Types の問題を追加しました。
コンテンツ セキュリティ ポリシーとは
コンテンツ セキュリティ ポリシー(CSP)を使用すると、ウェブサイトでの特定の動作を制限してセキュリティを強化できます。たとえば、CSP を使用してインライン スクリプトを禁止したり、eval を禁止したりできます。どちらも、クロスサイト スクリプティング(XSS)攻撃の攻撃対象領域を減らすことができます。CSP の詳細については、こちらをご覧ください。
特に新しい CSP は Trusted Types(TT)ポリシーです。これにより、ウェブサイトに対する大規模なインジェクション攻撃を体系的に防止できる動的分析が可能になります。これを実現するために、TT はウェブサイトの JavaScript コードを監視し、innerHTML などの DOM シンクに特定の種類の要素のみを割り当てられるようにします。
ウェブサイトは、特定の HTTP ヘッダーを含めることで、コンテンツ セキュリティ ポリシーを有効にできます。たとえば、ヘッダー content-security-policy: require-trusted-types-for 'script'; trusted-types default は、ページの TT ポリシーを有効にします。
各ポリシーは、次のいずれかのモードで動作します。
- 自動適用モード - すべてのポリシー違反がエラーになります。
- レポート専用モード - エラー メッセージを警告として報告しますが、ウェブページでエラーは発生しません。
[問題] タブのコンテンツ セキュリティ ポリシーの問題の実装
この取り組みの目標は、CSP の問題のデバッグ エクスペリエンスを改善することでした。新しい問題を検討する際、DevTools チームは、おおよそ次のプロセスに従います。
- ユーザー ストーリーの定義。ウェブ デベロッパーが問題を調査する方法をカバーする、DevTools フロントエンドの一連のユーザー ストーリーを特定します。
- フロントエンドの実装。ユーザー ストーリーに基づいて、フロントエンドでの問題の調査に必要な情報(関連するリクエスト、Cookie の名前、スクリプトまたは HTML ファイルの行など)を特定します。
- 問題の検出。Chrome で問題を検出できるブラウザ内の場所を特定し、ステップ(2)の関連情報を含む問題を報告する場所を実装します。
- 問題を保存して表示する。問題を適切な場所に保存し、DevTools が開いたら利用できるようにする
- 問題のテキストを設計する。ウェブ デベロッパーが問題を理解し、さらに重要なこととして、問題を修正するのに役立つ説明文を作成する
ステップ 1: CSP の問題に関するユーザー ストーリーを定義する
実装作業を開始する前に、必要な作業をよりよく理解するために、ユーザー ストーリーを含む設計ドキュメントを作成しました。たとえば、次のようなユーザー ストーリーを記述しました。
ウェブサイトの一部がブロックされていることに気づいたデベロッパーとして、次のことを行いたいと考えています。 - ...ウェブサイトで iframe や画像がブロックされている原因が CSP であるかどうかを確認する - ...特定のリソースのブロックの原因となっている CSP ディレクティブを特定する - ...現在ブロックされているリソースの表示や、現在ブロックされている JS の実行を許可するように、ウェブサイトの CSP を変更する方法を把握する。
このユーザー ストーリーを検証するため、関心のある CSP 違反を示すシンプルなサンプル ウェブページをいくつか作成し、サンプルページを検証してプロセスに慣れるようにしました。以下に、ウェブページの例をいくつか示します([問題] タブを開いた状態でデモを開きます)。
このプロセスを通じて、CSP の問題のデバッグにおいて最も重要な情報はソースの場所であることがわかりました。また、リソースがブロックされた場合に関連する iframe とリクエストをすばやく見つけることができ、DevTools の [Elements] パネルの HTML 要素への直接リンクも役立つことがわかりました。
ステップ 2: フロントエンドの実装
この分析情報を基に、Chrome DevTools Protocol(CDP)を介して DevTools で利用できるようにしたい情報の最初のドラフトを作成しました。
以下は、third_party/blink/public/devtools_protocol/browser_protocol.pdl からの抜粋です。
type ContentSecurityPolicyIssueDetails extends object
properties
# The url not included in allowed sources.
optional string blockedURL
# Specific directive that is violated, causing the CSP issue.
string violatedDirective
boolean isReportOnly
ContentSecurityPolicyViolationType contentSecurityPolicyViolationType
optional AffectedFrame frameAncestor
optional SourceCodeLocation sourceCodeLocation
optional DOM.BackendNodeId violatingNodeId
上記の定義は、基本的に JSON データ構造をエンコードします。PDL(プロトコル データ言語)と呼ばれるシンプルな言語で記述されています。PDL は 2 つの目的で使用されます。まず、PDL を使用して、DevTools フロントエンドが依存する TypeScript 定義を生成します。たとえば、上記の PDL 定義では、次の TypeScript インターフェースが生成されます。
export interface ContentSecurityPolicyIssueDetails {
/**
* The url not included in allowed sources.
*/
blockedURL?: string;
/**
* Specific directive that is violated, causing the CSP issue.
*/
violatedDirective: string;
isReportOnly: boolean;
contentSecurityPolicyViolationType: ContentSecurityPolicyViolationType;
frameAncestor?: AffectedFrame;
sourceCodeLocation?: SourceCodeLocation;
violatingNodeId?: DOM.BackendNodeId;
}
2 つ目として、おそらくこちらの方が重要ですが、定義から C++ ライブラリを生成します。このライブラリは、これらのデータ構造を生成して C++ Chromium バックエンドから DevTools フロントエンドに送信する処理を行います。このライブラリを使用すると、次の C++ コードを使用して ContentSecurityPolicyIssueDetails オブジェクトを作成できます。
protocol::Audits::ContentSecurityPolicyIssueDetails::create()
.setViolatedDirective(d->violated_directive)
.setIsReportOnly(d->is_report_only)
.setContentSecurityPolicyViolationType(BuildViolationType(
d->content_security_policy_violation_type)))
.build();
提供する情報を決定したら、その情報を Chromium のどこから取得するかを検討する必要がありました。
ステップ 3: 問題の検出
前のセクションで説明した形式で Chrome DevTools Protocol(CDP)で情報を利用できるようにするには、バックエンドで実際に情報を利用できる場所を見つける必要がありました。幸いなことに、CSP コードにはすでにレポート専用モードで使用されるボトルネックがあり、そこにフックすることができました。ContentSecurityPolicy::ReportViolation は、CSP HTTP ヘッダーで構成できる(オプションの)レポート エンドポイントに問題を報告します。レポートに含めたい情報のほとんどはすでに利用可能だったため、計測を機能させるためにバックエンドを大きく変更する必要はありませんでした。
ステップ 4: 問題を保存して表示する
少し複雑なのは、コンソール メッセージの処理方法と同様に、DevTools が開かれる前に発生した問題も報告したいということです。つまり、問題はフロントエンドに直接報告されるのではなく、DevTools が開いているかどうかにかかわらず、問題が入力されたストレージが使用されます。DevTools が開かれると(または、他の CDP クライアントが接続されると)、以前に記録されたすべての問題がストレージから再生されます。
これでバックエンドの作業は完了しました。次は、フロントエンドで問題をどのように表示するかに焦点を当てる必要がありました。
ステップ 5: 課題のテキストを設計する
問題のテキストを設計するプロセスには、DevTools チーム以外の複数のチームが関与します。たとえば、機能の実装チーム(この場合は CSP チーム)や、ウェブ デベロッパーが特定の問題に対処する方法を設計する DevRel チームの分析情報を参考にすることがよくあります。通常、問題のテキストは、完成するまで何度か修正されます。
通常、DevTools チームは、想定している内容の大まかなドラフトから始めます。
## Header
Content Security Policy: include all sources of your resources in content security policy header to improve the functioning of your site
## General information
Even though some sources are included in the content security policy header, some resources accessed by your site like images, stylesheets or scripts originate from sources not included in content security policy directives.
Usage of content from not included sources is restricted to strengthen the security of your entire site.
## Specific information
### VIOLATED DIRECTIVES
`img-src 'self'`
### BLOCKED URLs
https://imgur.com/JuXCo1p.jpg
## Specific information
https://web.dev/strict-csp/
反復処理の結果、次のようになりました。

このように、機能チームと DevRel を巻き込むことで、説明がより明確かつ正確になります。
ページ上の CSP の問題は、CSP 違反専用のタブでも確認できます。
Trusted Types の問題のデバッグ
適切なデベロッパー ツールがないと、TT を大規模に扱うのは困難な場合があります。
コンソール印刷の改善
信頼できるオブジェクトを扱う場合は、信頼できないオブジェクトと同等以上の情報を表示する必要があります。残念ながら、現時点では、Trusted Object を表示するときに、ラップされたオブジェクトに関する情報は表示されません。
これは、コンソールに表示される値が、デフォルトでオブジェクトの .valueOf() を呼び出すことで取得されるためです。ただし、Trusted Type の場合、返される値はあまり役に立ちません。代わりに、.toString() を呼び出したときに取得されるものと同様のものを取得する必要があります。これを実現するには、V8 と Blink を変更して、信頼できる型のオブジェクトを特別に処理する必要があります。
歴史的な理由から、このカスタム処理は V8 で行われていましたが、このアプローチには重大なデメリットがあります。カスタム表示が必要なオブジェクトは多数ありますが、JS レベルでは同じタイプです。V8 は純粋な JS であるため、Trusted Type などのウェブ API に対応するコンセプトを区別できません。そのため、V8 はエンベッダー(Blink)にそれらを区別するための支援を求める必要があります。
そのため、コードのその部分を Blink またはエンベッダーに移動することは、論理的な選択のように思えます。この問題以外にも、次のような多くのメリットがあります。
- 各エンベッダーは独自の説明生成を持つことができます
- Blink API を使用して説明を生成する方がはるかに簡単です。
- Blink はオブジェクトの元の定義にアクセスできます。したがって、
.toString()を使用して説明を生成する場合、.toString()が再定義されるリスクはありません。
違反時のブレーク(レポート専用モード)
現在、TT 違反をデバッグする唯一の方法は、JS 例外にブレークポイントを設定することです。強制された TT 違反は例外をトリガーするため、この機能は多少なりとも有用です。しかし、実際のシナリオでは、TT 違反をより細かく制御する必要があります。特に、TT 違反(他の例外ではない)でのみブレークし、レポート専用モードでもブレークし、さまざまな種類の TT 違反を区別したいと考えています。
DevTools はすでにさまざまなブレークポイントをサポートしているため、アーキテクチャは非常に拡張性に優れています。新しいブレークポイント タイプを追加するには、バックエンド(Blink)、CDP、フロントエンドを変更する必要があります。新しい CDP コマンド(setBreakOnTTViolation)を導入する必要があります。このコマンドは、フロントエンドがバックエンドに、どのような種類の TT 違反で中断すべきかを伝えるために使用されます。バックエンド(特に InspectorDOMDebuggerAgent)は、TT 違反が発生するたびに呼び出される「プローブ」onTTViolation() を提供します。次に、InspectorDOMDebuggerAgent は、その違反がブレークポイントをトリガーするかどうかを確認し、トリガーする場合は、フロントエンドにメッセージを送信して実行を一時停止します。
これまでの取り組みと今後の予定
ここで説明する問題が導入されてから、[問題] タブはかなり変更されています。
- DevTools の他のパネルとの相互接続性が向上しました。
- [問題] タブに、低コントラスト、Trusted Web Activity、後方互換モード、Attribution Reporting API、CORS 関連の問題など、さらに多くの問題が報告されるようになりました。
- 問題を非表示にする機会が導入されました
今後は、[問題] タブを使用してより多くの問題を表示する予定です。これにより、長期的には、読み取り不可能なエラー メッセージ フローのコンソールをアンロードできるようになります。
プレビュー チャネルをダウンロードする
Chrome の Canary、Dev、Beta のいずれかをデフォルトの開発用ブラウザとして使用することを検討してください。これらのプレビュー チャンネルでは、最新の DevTools 機能にアクセスしたり、最先端のウェブ プラットフォーム API をテストしたり、ユーザーよりも先にサイトの問題を見つけたりできます。
Chrome DevTools チームに問い合わせる
DevTools の新機能、更新、その他の関連事項について話し合うには、次のオプションを使用します。
- フィードバックや機能リクエストは、crbug.com からお送りください。
- DevTools のその他 > [ヘルプ] > [DevTools の問題を報告] を使用して、DevTools の問題を報告してください。
- @ChromeDevTools をメンションしてツイートしてください。
- DevTools の新機能に関する YouTube 動画または DevTools のヒントに関する YouTube 動画にコメントを残す。