🍱

今Partytownがヤバい。JavaScript Sandboxの未来はどっちだ?

2021/10/06に公開

概要

Partytownというプロジェクトが先月発表された。このプロジェクト自体はWebのパフォーマンス向上(3rd Party Scriptによるブロッキングの低減)を主目的としているが、実質ブラウザにおけるJavaScript Sandboxの方向性に一石を投じるものであるとして自分は理解した。本稿ではこちらについて背景とともに解説を試みる。

WebブラウザにおけるJavaScript Sandbox

JavaScriptで記述されたWebアプリケーションにおいて、たとえばプラグイン機構を実現したいなど、他Partyが提供あるいはユーザ自身が記述したスクリプトを、ホストとなるアプリケーションに影響を与えることなく実行することを許可したい、というケースはままある。2000年代に跋扈したブログパーツの類はWebコンテンツに対するプラグインの代表例とも言えるが、埋め込み先ページに対しての全権を与えるしかなく、なかなかハードボイルドな世界であった。

JavaScriptのSandboxを実現する方法といえば、古典的にはiframeで環境を分離し、ホスト側との通信はpostMessasgeで行う形となる。iframeをホストとは異なるドメインから提供してプラグインとなるJavaScriptを実行する環境として適用することで、ブラウザのSame Origin Policyにセキュリティ制御を任せることができたが、いかんせんiframeは作成および維持ともに重い処理である。

JavaScript Sandboxの選択肢

JavaScript Sandboxについて、2021年現在、古典的な実装方法以外の有望な方向性は以下の3つに分類されるように思われる。

ShadowRealm

JavaScriptの実行環境内に分離されたグローバル環境を用意するための標準仕様としてtc39にて議論されている。2021年10月現在でstage 3。最近Realmsから名前が変わってこのとおりとなった。ShadowRealmが実現することはNode.jsに詳しい人はvmモジュールが行っていることと同様と考えても良いかもしれない。

ただしこれがセキュアなSandboxとして機能するのかについては議論がある。特に近年Spectreによるサイドチャネル攻撃の可能性がWebというものに与えたインパクトについて無視ができない。ChromeはこれによってSite Isolationを急速に進める形となった。

そしてShadowRealmは同一プロセス内で稼働するという性質上、Spectre等を念頭に入れれば達成できるのはIsolation(分離)のみであり、そこにSecurity Boundaryがあることを求めてはいけないと見える。そして本質的にそのような「安全でない」Sandboxであるのにも関わらず、それを標準に含めて大丈夫なのか、という意見もある

本仕様のチャンピオンメンバーであるCaridy Patiñoが所属しているSalesforceにおいては、自社のLightningプラットフォームのセキュリティの根幹に位置するLightning Locker機構を標準準拠したものにするためにRealms(ShadowRealm)を推し進めていた節もあり、ShadowRealmがそのような状態であると屋台骨がゆらぎかねないのも事情を複雑にしているように見える。

なお、ShadowRealmは標準化の途中でWebブラウザに広く実装される前であるため、その活用にはShimを利用する必要があった。2年ほど前にそのShim実装に脆弱性があることが発覚し、それを利用していたFigmaのプラグイン機構が方針転換を余儀なくされたという経緯もある(ただ一応この事自体はShadowRealmの仕様そのものの脆弱性とは関係はない)。

Web Worker

Web Worker(以下単にWorkerとも)は、メインスレッドと別のバックグランドのスレッドを用意してJavaScript処理を実行する。iframeほど古典ではないが、案外歴史はあるといっていい。ブラウザサポートもかなり前からでIEも含めてほとんどの環境で動く。ただ、Sandboxとしてというよりいわゆる"Off the main thread"としてパフォーマンスの文脈からIsolationの利点を語られることが多いという印象がある。

Sandboxとしての要件は、完全に分離されるのではなくホスト環境との通信ができてなんぼの価値だろう。Workerの最大の懸念点はそれがpostMessageという非同期のメッセージングにのみに依っているところである。これによって同期的なインターフェースを持つホスト環境の資源(localStorageやDOMプロパティなど)へのアクセスを同期のまま実施するのが厳しくなっており、そのような用途ではなかなか使いづらかった。

ただし、WorkerはSite Isolationによってプロセス分離が可能になるという点で、Spectre以降のSandboxとしてのセキュリティに求められるものは満たしやすい位置にいると思われる。

Wasm (WebAssembly)

Wasm(WebAssembly)は御存知の通りJavaScriptに限らずどんな言語でもコンパイルされればブラウザ上で動作できるという触れ込みで登場した。それはそれでメリットなのだが、それはWasm上でJavaScriptを動かすことも可能ということでもある!...何かの冗談のようだが、ネタというわけでもない。実際、Wasm上で稼働できる代表的なJS実行環境としてQuickJSがあるし、実際に活用もされている。

Wasm自体がもともとメモリ空間などホストとは分離された環境として用意されているので、それを利用したSandboxとしての利用も考えられている。Worker同様にDOMへの直接アクセスは隠蔽されているが、ホストとの通信は同期的であるため、Wasm上でDOM操作する仕組みもストレートに実現できる。

ただし基本は同一のプロセス内で動作するものなので、Spectreを考慮しても、つまりサイドチャネル攻撃を前提としてもSandboxとして問題ない状況かと言われると、それはShadowRealmの場合と同様となるのではないかと思われる。最近Swivel: Hardening WebAssembly against Spectreという論文が出ているが(詳しくは見ていないため中身の言及は控える)、つまりこれは課題として認識されており解決を模索されている状態であるということが窺える。

ちなみに前述のFigmaはRealmsのShimに見切りをつけた後はWasm QuickJSでのプラグイン実装に切り替えた。素早い。

Partytownによるブレイクスルー

さて、Spectre等によるサイドチャネル攻撃の緩和のためにプロセス分離を実装することがWebブラウザにおいて止められない流れとなっていることは先に述べたが、先述の3つのSandbox候補のうちそれが可能なWorkerはいかんせんpostMessage頼みというのがネックであった。

特にWebアプリケーションにおいてはDOMの取り扱いが問題になる。Worker上で仮想DOM環境を構築するworker-domでもこれは非同期アクセスの代替を設けることでクリアしようとしたが、本質的に既存のアプリケーションに書き換えを強いるものであり、主流としての先行きが明るいとはちょっと思えなかった。

たとえばdocument.body.clientWidthといったホスト環境内のDOMのプロパティを参照する場合を考えてみよう。WorkerによるSandboxの世界でホスト環境のその情報を得るためには、postMesasgeを介する必要がある以上、今まではどうしてもどうしても非同期形式(コールバックやPromiseなど)に変換する必要があった。このことは既存コードの書き換えの必要性を意味しており、同期DOMを前提として作られている各種ライブラリ等をそのSandbox環境下でそのまま動かすことはできない、ということになる。

この状況を一変させうるのが冒頭に述べたpartytownである。PartytownはWorker上で3rd Partyのスクリプトを動かしていながら、コードの書き換えなしでDOMなどホストに存在するリソースに同期的にアクセスできるという。

Partytownはちょっとしたhackでこれを成し遂げているわけであるが、モダンなJavaScriptに慣れ親しんだ我々にとってはとても「レガシー」でかつ積極的な活用は忌避すべきとされている 「同期的なXMLHttpRequest呼び出し」 と、Workerよりはまだ環境制約は多いが同じくモダンなWeb標準である 「Service Worker」 を使って解決したところが大変おもしろいので、以下に何をしているのか簡単に紹介する。

Partytownが何をしているか

Partytownが何をしているか、先に挙げたdocument.body.clientWidthへのアクセスを例に解説してみる。

Worker内での仮想的なDOM環境を用意

最初にWorker内でDOMのインターフェースを再実装する必要がある。かなりシビアだが、worker-dom などでもやっているわけなので力技でできないこともない話である。

とりあえずdocument.bodyに相当するオブジェクトを定義し、そこにclientWidthというプロパティを用意することを考える。

プロパティアクセスをXMLHttpRequestの呼び出しに変換する

このプロパティのgetter関数実装では、自らのノードの情報とアクセスされたプロパティの情報をURLとしてシリアライズし、XMLHttpRequestでコールする。このURLはサーバなどインターネットに存在するURLではなく、内部で一意に命令を判定できるようになっていればよい。

ただし、このXMLHttpRequestによる呼び出しは、同期的に 実行する必要がある。これにより、レスポンスが帰ってくるまでの間のWorker内のスレッドはブロックされる。XMLHttpRequestの同期呼び出しなど、バッドプラクティスとしての例しかもはや転がっていなかろうし、そもそも近年では存在すら知られていないかもしれない。ちなみにXMLHttpRequestのモダンな代替であるfetch()では非同期しかないため、これができない。

Service WorkerでURLをインターセプトする

さて、XMLHttpRequestでリクエストをコールしたはいいが、インターネット上にそのようなURLでアクセスできるところは存在しない。そのため何もしなければエラーになる。ここでService Workerが出てくる。

Service Workerはそのサイトからの任意のURLコールを横取り(インターセプト)できるため、マッチするパターンのURLがあればそれを内部処理としてService Worker内で処理することができる。

Service WorkerはWorkerと同様にpostMessageでホストと非同期通信できる。ここで、URLに含まれているDOMのノードとプロパティアクセスの命令をそのままホストに送り込む。

あとはホスト側のスクリプトがその命令を解釈し、document.body.clientWidthの内容を読み取って、これまたService WorkerにpostMessageして返す。

Service Workerは、ホストから送られてきたその情報を、インターセプトしたURLリクエストのレスポンスとして返す。

すると元のWorkerではdocument.body.clientWidthのアクセスの結果として、実際にホスト環境でdocument.body.clientWidthとしてアクセスしたものの結果を同期的に得ることができる。

今後の課題

Partytownはいわゆる「very alpha」のプロジェクトであり、現在の時点では今後の継続性も正直わからない。ただ、Partytownの価値は、そのプロジェクトそのものよりも、Workerからのホスト環境へのアクセスが同期的に実行できるというPoCを示したことにあるのではないか。これによってWorkerを使ったJavaScript Sandboxは有力な候補として盛り返しがあるのではないかと考えている。

ただし非同期/同期の変換や何度ものpostMessageによるブリッジはオーバーヘッドを容易に想像できる。今後の検証によってはただの机上の可能性だけだったね、で終わるかもしれないし、はたまた効率的なパイプラインの仕組みを根性で実装してくるかもしれない。

そんな不確定な要素が多い中、なぜ「Partytownがヤバい」などとして記事を書いたかというと、過去のWebにおいてさまざまなhackが進化を後押しをしてきたことは否めず、そこにいたる過程にはなんとなく抑圧や制限からの解放の際に発する一瞬の煌きのようなものを感じるからである。エモいね。完。

Discussion