Chrome の拡張機能システムは、かなり厳しいデフォルトのコンテンツ セキュリティ ポリシー(CSP)を適用しています。ポリシーによる制限は単純で、スクリプトを個別の JavaScript ファイルから別の JavaScript ファイルに移動する必要があります。また、addEventListener
を使用するようにインライン イベント ハンドラを変換し、eval()
を無効にする必要があります。Chrome アプリにはより厳密なポリシーがあり、Google はこれらのポリシーが提供するセキュリティ プロパティに満足しています。
ただし、さまざまなライブラリが、パフォーマンスの最適化と表現の容易さのために、new Function()
などの eval()
および eval
に似た構造を使用していることを認識しています。テンプレート化ライブラリは、このスタイルの実装が特に発生しがちです。一部の(Angular.js など)は CSP をすぐにサポートしていますが、一般的なフレームワークの多くは拡張機能の eval
レス環境と互換性のあるメカニズムにまだ更新されていません。したがって、この機能に対するサポートの削除は、デベロッパーにとって予想以上に問題となることがわかっています。
このドキュメントでは、セキュリティを損なうことなくこれらのライブラリをプロジェクトに組み込むための安全なメカニズムとして、サンドボックス化を紹介します。簡潔にするために拡張機能という用語を使用しますが、この概念はアプリケーションにも同様に適用されます。
サンドボックスを選ぶ理由
eval
は、実行されるコードは拡張機能の高権限環境内のすべてにアクセスできるため、拡張機能内では危険です。ユーザーのセキュリティとプライバシーに深刻な影響を与える可能性がある強力な chrome.*
API が数多く用意されています。データの引き出しの被害は最小限で済みます。そこで提供されているソリューションは、eval
が拡張機能のデータまたは拡張機能の高価値 API にアクセスせずにコードを実行可能なサンドボックスです。データも API もなく、問題ありません。
これは、拡張機能パッケージ内の特定の HTML ファイルをサンドボックス化してリストすることで実現できます。サンドボックス化されたページが読み込まれると、そのページは一意の生成元に移動され、chrome.*
API へのアクセスが拒否されます。このサンドボックス化されたページを iframe
を介して拡張機能に読み込む場合、メッセージを渡し、なんらかの方法でそれらのメッセージを処理して、結果が返されるまで待ちます。このシンプルなメッセージ メカニズムにより、eval
に基づくコードを拡張機能のワークフローに安全に組み込むために必要なものがすべて揃っています。
サンドボックスを作成して使用する。
コードをすぐに確認したい場合は、サンドボックス化のサンプル拡張機能をご利用ください。これは、Handlebars テンプレート ライブラリの上に構築された小さなメッセージ API の実例であり、作業に必要なものがすべて揃っています。さらに詳しく知りたい方のために こちらのサンプルを一緒に見てみましょう
マニフェスト内のファイルを一覧表示する
サンドボックス内で実行する各ファイルは、sandbox
プロパティを追加して拡張機能のマニフェストにリストする必要があります。これは重要なステップであり、忘れがちなため、サンドボックス化されたファイルがマニフェストに記載されていることを再度確認してください。このサンプルでは、「sandbox.html」という名前のファイルをサンドボックス化しています。マニフェスト エントリは次のようになります。
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
サンドボックス化されたファイルを読み込む
サンドボックス化されたファイルで何か興味深い処理を行うには、拡張機能のコードで処理できるコンテキストでファイルを読み込む必要があります。ここでは、sandbox.html は、iframe
を介して拡張機能のイベントページ(eventpage.html)に読み込まれています。eventpage.js には、ページ上で iframe
を見つけ、その contentWindow
で postMessage
メソッドを実行し、ブラウザ アクションがクリックされるたびにサンドボックスにメッセージを送信するコードが含まれています。メッセージは、context
と command
の 2 つのプロパティを含むオブジェクトです。この両方については後ほど説明します。
chrome.browserAction.onClicked.addListener(function() {
var iframe = document.getElementById('theFrame');
var message = {
command: 'render',
context: {thing: 'world'}
};
iframe.contentWindow.postMessage(message, '*');
});
postMessage
API の一般的な情報については、MDN の postMessage
ドキュメント をご覧ください。非常に完成しており、読む価値があります。特に、データはシリアル化可能な場合にのみやり取りできます。たとえば、関数はそうではありません。危険な行為
sandbox.html
が読み込まれると、Handlebars ライブラリが読み込まれ、Handlebars が提案する方法でインライン テンプレートを作成およびコンパイルされます。
<script src="handlebars-1.0.0.beta.6.js"></script>
<script id="hello-world-template" type="text/x-handlebars-template">
<div class="entry">
<h1>Hello, !</h1>
</div>
</script>
<script>
var templates = [];
var source = document.getElementById('hello-world-template').innerHTML;
templates['hello'] = Handlebars.compile(source);
</script>
これは失敗に終わりません。Handlebars.compile
が new Function
を使用しても、想定どおりの動作となり、コンパイルされたテンプレートは templates['hello']
に格納されます。
結果を返す
このテンプレートを使用するには、[イベント] ページからコマンドを受け取るメッセージ リスナーをセットアップします。渡された command
を使用して、必要な処理を決定します(単なるレンダリング以上の作業を行うことも考えられます。テンプレートの作成、なんらかの方法で管理など)で、context
はレンダリングのためにテンプレートに直接渡されます。レンダリングされた HTML はイベントページに返されます。これにより、拡張機能は後でそれを使用して有用な処理を行うことができます。
<script>
window.addEventListener('message', function(event) {
var command = event.data.command;
var name = event.data.name || 'hello';
switch(command) {
case 'render':
event.source.postMessage({
name: name,
html: templates[name](event.data.context)
}, event.origin);
break;
// case 'somethingElse':
// ...
}
});
</script>
[イベント] ページに戻ると、このメッセージが表示され、渡された html
データを使用して興味深い処理を行います。この例では、単にデスクトップ通知を介してエコー出力しますが、この HTML を拡張機能の UI の一部として安全に使用することもできます。innerHTML
を使用して挿入しても、セキュリティ リスクは大きくありません。巧妙な攻撃によってサンドボックス化されたコードを完全に侵害したとしても、権限の高い拡張機能のコンテキストに危険なスクリプトまたはプラグインのコンテンツを挿入できなくなるからです。
このメカニズムにより、テンプレート化が簡単になりますが、テンプレートの作成に限ったものではありません。厳格なコンテンツ セキュリティ ポリシーに基づき、最初から機能しないコードはすべてサンドボックス化できます。実際、正しく実行される拡張機能のコンポーネントをサンドボックス化して、プログラムの各部分を適切な実行に必要な最小限の権限セットに制限することが有用な場合がよくあります。Google I/O 2012 で発表された「Writing Secure Web Apps and Chrome Extensions」プレゼンテーションでは、これらの手法の実例をいくつか紹介しています。所要時間は 56 分です。