Caputchin
Caputchin を理解する

Caputchin がゲームをどうサンドボックス化するか

Markdown で表示

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-srcCaputchin 自身のインラインランタイムのハッシュ + ゲームバンドルのオリジン + 'wasm-unsafe-eval'Caputchin の正確なブートストラップ('unsafe-inline' ではなく sha256- ハッシュで固定)とゲーム自身のバンドルだけを走らせる。'wasm-unsafe-eval' は、完全な 'unsafe-eval' を付与せずに WASM エンジンがコンパイルできるようにする。
connect-src'none'持ち出し防止のディレクティブ。 ゲームはどこへも fetch、XHR、WebSocket を開く、ビーコンができません。ネットワークの送出は一切なし。
img-srcdata:(加えて、下記のスキンアセットのオリジン)スプライトはインライン、または許可されたアセットのホストから。
media-srcdata:(加えて、下記のスキンアセットのオリジン)音声/動画はインライン、または許可されたアセットのホストから。
font-srcdata:インラインのフォントのみ。
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 は、別の、補完的な制御です。それは あらゆるものからページを 守ります。

この節のすべてを動かしている事実が一つあります。ゲームのフレームは、あなたのページのポリシーを継承します。 フレームはインラインの srcdoc ドキュメントで、ブラウザはそうしたドキュメントに、フレーム自身のポリシーに加えて埋め込み元ページの CSP を適用します。つまり上で示したフレームの固く閉じたポリシーは、Caputchin が足す下限であって、あなたのポリシーの代わりではありません。フレームの中で走るものは 両方 を満たす必要があり、それはつまり、ゲームのバンドルとフレーム自身のブートストラップを、あなたのページのポリシーが許可しなければならないということです。どちらもページが直接読み込むものではないのに、です。

最小限、ウィジェットは次を要します:

ディレクティブ許可なぜ
script-srcウィジェットのスクリプトを読み込むオリジン、加えて 'unsafe-eval'<caputchin-widget> / <caputchin-game> を定義する <script>、選んだ CDN(jsDelivr、あなた自身のホスト、または caputchin.com)。'unsafe-eval' は 計測 のチャレンジを走らせます。計測がオンの間は必須で、キーの セキュリティ ページで計測をオフにすれば落とせます。
script-src(ゲームのみ)ゲームのバンドルのオリジン、加えて 'unsafe-inline' と 'wasm-unsafe-eval'フレームがこのポリシーを継承するからです。バンドルのオリジンは、マーケットプレイスのゲームなら https://games.caputchin.com、セルフホストのゲーム ならあなた自身のホストか CDN です。'unsafe-inline' はフレーム自身のブートストラップスクリプトを賄います。フレームはそれを sha256- ハッシュで固定しますが、あなたのページには固定できません。そのハッシュはウィジェットのリリースごとに変わるからです。'wasm-unsafe-eval' は WebAssembly エンジンの上に作られたゲームが必要とします。ゲームなしで <caputchin-widget> だけを使うなら、これらはどれも当てはまりません。
connect-srchttps://verify.caputchin.com、加えてウィジェットのスクリプトを読み込むオリジン別々の二つの通信です。ウィジェットは、検証を用意して確認するために Caputchin の検証サービスを呼ぶので、その正確なオリジンを許可してください(ウィジェットを別の API ホストに向けるなら、代わりにそれを許可してください)。さらに proof-of-work のソルバーは、ウィジェットのスクリプトの隣から WebAssembly モジュールを取ってくるので、その CDN のオリジンは script-src と同じくここにも要ります。ウィジェットを自分のオリジンでホストしているなら、二つ目は 'self' で賄えます。
worker-srcblob:proof-of-work のソルバーは、blob: URL から作られた Web Worker の中で完全に走り、メインスレッドのフォールバックがないので、これは必須です。worker-src を省くと、ブラウザは child-src、それから default-src にフォールバックし、default-src 'self' だけでは blob: を許可しません。

proof-of-work のソルバーを高速な WebAssembly として走らせるには、script-src に 'wasm-unsafe-eval' を加えてください。このキーワードも、ソルバーが取りに行くオリジンも、検証が成功するために必須ではありません。どちらか一方でも欠けると、ソルバーはより遅い純粋な JavaScript の実装(なお Worker の中)にフォールバックし、検証は引き続き動きます。ただし、二つは揃って初めて効きます。ソルバーは WebAssembly モジュールを fetch してからコンパイルするので、速い経路には connect-src のウィジェットのスクリプトのオリジン と script-src の 'wasm-unsafe-eval' の両方が要ります。片方だけを許可すると、遅いソルバーのまま、理由を告げるものはコンソールに何も出ません。

ゲームのために frame-src のエントリは 要りません。ゲームのフレームは、URL から取ってきたドキュメントではなくインラインの srcdoc ドキュメントなので、frame-src が照合できる URL がそもそもなく、frame-src 'none' の下でもフレームは読み込まれます。

要る もので、しかも人を驚かせるのは、あなたのページの script-src に入れるゲームのバンドルのオリジンです。ページがそのバンドルを読み込むことは決してないのに、です。読み込むのはフレームで、フレームは自身のポリシーに加えてあなたのポリシーの下でも走ります。フレームのブートストラップと、WebAssembly のゲームエンジンも同じです。これらを許可しても、ゲームが届く先が広がることはありません。フレーム自身のポリシーは依然として connect-src 'none' を固定し、フレームは依然として不透明オリジンで、あなたのページが与えた許可が、フレームが自分に課した下限を緩めることはできません。

script-src の 'unsafe-inline' はこの一覧の中で居心地の悪い部分で、そう言うのが誠実です。今日ゲームを走らせるには必須で、あなた自身のページのインラインスクリプト防御を弱め、代替策(フレームのブートストラップの sha256- ハッシュを固定する)は、そのハッシュがウィジェットのリリースごとに動く間は現実的ではありません。あるページでその取引が受け入れられないなら、そこでは <caputchin-widget> を使ってください。ゲーム専用の行は何一つ要りません。

Caputchin の要素が CSP の下で黙って読み込みに失敗したら、ブラウザのコンソールがブロックされたディレクティブを名指します。それが名指すオリジンかキーワードをそのディレクティブに加えてください。厳格に始め、コンソールが求めるものちょうどを開いてください。そして about:srcdoc に対して報告された違反も、自分のページに対する違反と同じだけ注意して読んでください。それはゲームのフレームが、継承したあなたのポリシーにぶつかっているもので、見落とされがちなのはまさにそこです。worker には blob:(worker-src)が要り、計測がオンの間は script-src に 'unsafe-eval' が要ります。'unsafe-eval' の要件は、キーの セキュリティ ページで計測をオフにすれば消えます。'unsafe-inline' が要るのは、上の表のとおりゲームを走らせるときだけです。proof-of-work の速い経路は任意ですが対になっています。script-src の 'wasm-unsafe-eval' と connect-src のウィジェットのスクリプトのオリジンです。この対だけは、コンソールが助けてくれない唯一のケースです。JavaScript のソルバーに落ちることはエラーではないからです。

動くポリシー

ウィジェットを jsDelivr から読み込み、マーケットプレイスのゲームを使い、計測はオンの場合:

default-src 'none';
script-src  https://cdn.jsdelivr.net https://games.caputchin.com 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval';
connect-src https://verify.caputchin.com https://cdn.jsdelivr.net;
worker-src  blob:;
img-src     'self' data: https:;
style-src   'unsafe-inline'

ゲームを使わないなら https://games.caputchin.com、'unsafe-inline'、'wasm-unsafe-eval' を落としてください。計測をオフにするなら 'unsafe-eval' を落としてください。ウィジェットをセルフホストするなら、script-src と connect-src の両方で https://cdn.jsdelivr.net を自分のオリジンに置き換えてください。

なぜこれほど多くの層か

各層は、それ自体で意味のある境界でしょう。一緒だと、1 つの失敗が破れではないことを意味します。null オリジンだけですでにゲームをデータから壁で隔て、CSP だけですでにネットワークの送出を殺し、完全性だけですでに正確なバイトを固定します。Caputchin はそのすべてを走らせます。信頼されないサードパーティのコードは、まさに多層防御が元を取る場所だからです。

あわせて読む

このページの内容