Caputchin がゲームをどうサンドボックス化するか
Caputchin のゲームは、あなたの訪問者のブラウザで、あなたのページで走るサードパーティのコードです。マーケットプレイスから来ようと、あなた自身がホストしようと、Caputchin はそれを 既定で信頼されないもの として扱い、いくつかの独立した隔離の層で包むので、バグのある、あるいは敵対的なゲームは、レンダリングし、遊び、結果を報告できますが、ほかには何にも到達できません。あなたのページにも、あなたの訪問者のデータにも、ネットワークにもです。
このページは、すべての手立て、それぞれがなぜ存在するか、そしてその全体が、あなた自身のサイトに加える CSP とどう相互作用するかを説明します。
一行での脅威モデル
ゲームは、サードパーティが供給する任意の JavaScript(そしておそらく WebAssembly)を走らせます。したがって隔離の目標は:ゲームは計算と描画ができ、結果をウィジェットに渡し戻せるが、自身のフレームの外側の何も読んだり影響したりできない。 以下のすべての層はその 1 つの目標に仕え、それらは多層防御で、どの単一の層も完璧だと信頼されません。
層 1:不透明オリジンのサンドボックス化された iframe
すべてのゲームは、ウィジェットが意図的に最小限の sandbox 属性で作る <iframe> の中で走ります。付与される 唯一の トークンは allow-scripts です。ゲームは JavaScript で、走らなければならないからです。ほかのすべては差し控えられ、最も重要な省略は allow-same-origin です。
allow-same-origin がないと、ブラウザはフレームに一意の null オリジン を与えます。その 1 つの事実が、荷重を支える境界です:
- ゲームはあなたのページの DOM、クッキー、
localStorage、または Caputchin のセッションを読めません。あなたのものとは異質な、使い捨てのオリジンに封じられています。 - フレームはまた、あなたのトップウィンドウをナビゲートしたり、ポップアップを開いたり、フォームを送信したり、ネイティブのダイアログを出したりも決してできない(それらの能力はすべて Caputchin が付与しないサンドボックストークンです)ので、ゲームからあなたのページへ戻るパスがありません。
allow-same-origin なしの allow-scripts の組み合わせが要点そのものです。ブラウザはゲームのコードを実行しますが、フレームを、あなたのものに何も触れられない異質なオリジンとして扱います。Caputchin はゲームのフレームに allow-same-origin を決して加えません。(結果として、ゲームがウィジェットにポストするすべてのメッセージはオリジン "null" から届き、ウィジェットのチャネルのチェックはそれを期待します。null でないオリジンは、それ自体が設定ミスのサンドボックスを示します。)
層 2:フレームのドキュメントは取得されず、インラインで作られる
ウィジェットは iframe をリモートのページに向けません。フレームの HTML ドキュメント全体を インラインで(srcdoc 経由で)構築し、それを null オリジンのフレームに渡します。そのドキュメントは小さいです。厳格な CSP の meta タグ(次の層)、小さな Caputchin ランタイムのブートストラップ、そしてゲームのバンドルを読み込む 1 つの <script> タグです。
これは両方の配信パスで同じ仕組みで、バンドルの URL だけが違います:
| ゲームのソース | インラインドキュメントのバンドル URL |
|---|---|
| マーケットプレイス(プラットフォームが解決) | Caputchin がマウント時に解決した、固定された完全性ハッシュ付きのバンドル URL。 |
セルフホスト(あなたの game-src) | あなたが供給した URL。 |
ドキュメントはゲームではなくウィジェットによって作られるので、ゲームは CSP、ランタイム、フレームの構造を決して制御せず、読み込まれた後に自身のバンドルがすることだけを制御します。
層 3:マーケットプレイスのゲームの Subresource Integrity
Caputchin はマーケットプレイスのゲームを提供するとき、バンドルを不変のバージョンに固定し、その暗号ハッシュ(SHA-384)を記録します。バンドルを読み込む <script> タグは、そのハッシュを crossorigin="anonymous" 付きの integrity 属性として運ぶので、固定されたものと 1 バイトでも違えば、ブラウザ自身 がバンドルの実行を拒みます。侵害された CDN は違うコードを差し替えられません。読み込みはフェイルクローズします。
セルフホストのゲームには、プラットフォームが主張するハッシュがありません(あなたが自分のバイトの信頼の根です)ので、integrity 属性はそのパスでは単に省かれます。マーケットプレイスのゲームの 結果 がどうサーバーで独立に再導出されるか、完全性とは別の保証については、リプレイ契約 を参照してください。
層 4:厳格なインライン Content-Security-Policy
インラインドキュメントはきつい Content-Security-Policy を運びます。フレームがすでに null オリジンでサンドボックス化されているにもかかわらず、CSP は読み込まれたコードができることをさらに制約します。既定ですべてを拒否し、最小限だけを再付与します:
| ディレクティブ | 値 | なぜ |
|---|---|---|
default-src | 'none' | 下で明示的に許可されないすべてを拒否。 |
script-src | Caputchin 自身のインラインランタイムのハッシュ + ゲームバンドルのオリジン + 'wasm-unsafe-eval' | Caputchin の正確なブートストラップ('unsafe-inline' ではなく sha256- ハッシュで固定)とゲーム自身のバンドルだけを走らせる。'wasm-unsafe-eval' は、完全な 'unsafe-eval' を付与せずに WASM エンジンがコンパイルできるようにする。 |
connect-src | 'none' | 持ち出し防止のディレクティブ。 ゲームはどこへも fetch、XHR、WebSocket を開く、ビーコンができません。ネットワークの送出は一切なし。 |
img-src | data:(加えて、下記のスキンアセットのオリジン) | スプライトはインライン、または許可されたアセットのホストから。 |
media-src | data:(加えて、下記のスキンアセットのオリジン) | 音声/動画はインライン、または許可されたアセットのホストから。 |
font-src | data: | インラインのフォントのみ。 |
style-src | 'unsafe-inline' | ゲームはインラインのスタイルを設定する。外部のスタイルシートはなし。(スタイルは、スクリプトやネットワークのようにはデータを持ち出せません。) |
正味の効果:ゲームは走り、レンダリングし、SDK のブリッジを通じて結果を報告し、ほかには何にも、特にネットワークには到達できません。この CSP は Caputchin が設定し、あなたが設定するものではありません。ゲームがどれだけ閉じ込められているかを正確に理解できるよう、ここに挙げています。
顧客の設定がフレームの CSP を広げる唯一の場所
スキンは、画像や音声のフィールドを、あなた自身の CDN の絶対 URL に向けられます(スキン を参照)。それらのアセットがロックダウンされたフレームの中で読み込まれるよう、Caputchin は それらの正確なアセットのオリジンだけ をフレームの img-src / media-src に加え、ほかのどこにも加えません。script-src と connect-src は決して広げられないので、許可されたアセットのオリジンでさえ、コードを読み込んだりネットワークのチャネルを開いたりに使えません。これが、設定された値がフレームのポリシーに影響する、単一の狭い方法で、それでもアセットだけです。
層 5:一方向の結果チャネル
ゲームはあなたのページへの呼び出せる面を持ちません。外への唯一の道は SDK のブリッジです。ゲームは、その不透明な結果(そのトレース)を、あなたのオリジンに住むウィジェットへ postMessage する単一のメソッドを呼びます。ウィジェットが Caputchin の API と話すものです。ゲームは決してしません(できません。connect-src が 'none' です)。なので信頼のパスは ゲーム → ウィジェット → あなたのバックエンド で、各ホップは結果を前へ渡すだけで、ゲームに後ろへの到達を決して付与しません。
あなた自身のページに設定する CSP
上の層は ゲームからあなたとあなたの訪問者を 守ります。あなた自身のページの Content-Security-Policy は、別の、補完的な制御です。それは あらゆるものからあなたのページを 守り、Caputchin は厳格なものの下で走るよう設計されています。Caputchin を埋め込むために CSP を緩める必要はありません。ウィジェットが正当に使う少数のオリジンを許可 すればよいのです。
最小限、ウィジェットは次を要します:
| ディレクティブ | 許可 | なぜ |
|---|---|---|
script-src | ウィジェットのスクリプトを読み込むオリジン、加えて 'unsafe-eval' | <caputchin-widget> / <caputchin-game> を定義する <script>、あなたが選んだ CDN(jsDelivr、あなた自身のホスト、または caputchin.com)。'unsafe-eval' は 計測 のチャレンジを走らせます。計測がオンの間は必須で、キーの セキュリティ ページで計測をオフにすれば落とせます。 |
connect-src | https://caputchin.com | ウィジェットは、検証を用意して確認するために Caputchin の API を呼びます。ウィジェットを別の API ホストに向けるなら、代わりにそれを許可してください。 |
frame-src | https://caputchin.com | ゲームの iframe を統べます。フレームはインラインの srcdoc ドキュメントを使うので、ブラウザはあなたの frame-src をそれに適用します。なのでここで Caputchin のオリジンを許可してください。 |
worker-src | blob: | proof-of-work のソルバーは、blob: URL から作られた Web Worker の中で完全に走り、メインスレッドのフォールバックがないので、これは必須です。worker-src を省くと、ブラウザは child-src、それから default-src にフォールバックし、default-src 'self' だけでは blob: を許可しません。 |
任意で script-src に 'wasm-unsafe-eval' を加えてください。それは proof-of-work のソルバーを高速な WebAssembly として走らせます。必須ではありません。なしでも、ソルバーはより遅い純粋な JavaScript の実装(なお Worker の中)にフォールバックします。
ページの CSP でゲーム自身のバンドルのオリジン(jsDelivr、ゲームの CDN、あなたの game-src のホスト)を許可する必要は ありません。そのバンドルは、上で述べたフレーム自身の CSP の下で、サンドボックス化されたフレームの中で 読み込まれ、あなたのページのポリシーの下ではありません。あなたのページは Caputchin をフレームに収めるだけで、Caputchin が中で走るものを閉じ込めます。
Caputchin の要素があなたの CSP の下で黙って読み込みに失敗したら、あなたのブラウザのコンソールがブロックされたディレクティブを名指します。それが名指すオリジンかキーワードをそのディレクティブに加えてください。厳格に始め、コンソールが求めるものちょうどを開いてください。'unsafe-inline' は決して要りません。worker には blob:(worker-src)が要り、計測がオンの間は script-src に 'unsafe-eval' が要ります。'unsafe-eval' の要件は、キーの セキュリティ ページで計測をオフにすれば消えます。'wasm-unsafe-eval' は任意です(proof-of-work のソルバーを速くします。さもなければ同じ Worker の中で JavaScript としてより遅く走ります)。
なぜこれほど多くの層か
各層は、それ自体で意味のある境界でしょう。一緒だと、1 つの失敗が破れではないことを意味します。null オリジンだけですでにゲームをあなたのデータから壁で隔て、CSP だけですでにネットワークの送出を殺し、完全性だけですでに正確なバイトを固定します。Caputchin はそのすべてを走らせます。信頼されないサードパーティのコードは、まさに多層防御が元を取る場所だからです。
あわせて読む
- ゲームがどうボットに抵抗するか:ゲームの完全性の、サーバー権威の側。
- リプレイ契約:ゲームの結果がどうサーバー側で独立に再導出されるか。
- セルフホストのゲームを作る:これらのサンドボックスが作者に課すバンドルの制約。
- CDN からウィジェットを読み込む:あなたのページの CSP が許可しなければならないスクリプトのオリジンを選ぶ。