LockerServiceでコンポーネントは本当にセキュアになりえるのか

## 本エントリについて

[LockerService](https://developer.salesforce.com/blogs/developer-relations/2016/04/introducing-lockerservice-lightning-components.html)が彼らの主張する通りほんとうにコンポーネントを「セキュア」に実行できるのかどうかについての考察（というより推測を多く含む）。

## Sandboxの実現方法

ここでは「Sandbox」の定義として、どんなにお行儀が悪い/悪意のあるコードがコンポーネントに入っていたとしても、DOM/BOMへのアクセスをコンテナで規定した範囲内でしか許されないように設定できること、とする。

ブラウザ環境で上記のSandboxを実現するためのアプローチとしては、自分の知る限り以下の2種類。

1. コンポーネントで用いられるJSのコードを静的解析・変換して該当のリソースへのアクセスを制御する
2. ブラウザの内蔵するセキュリティ機構を使って実現する

ここで挙げた1. に関しては、古典的ではあるが[Caja](https://developers.google.com/caja/)がよく知られている。JSのコード解析を行いSandbox化されたコードとしてコンパイルするものであり、それこそOpenSocialの時代から存在するが、残念ながらGoogleの一部サービスに組み込まれた以外に一般的に採用されている事例は少なく、成功したとは言いがたい状況である。

2.のアプローチは基本的にはiframeを利用することになる。そしてセキュリティを担保するものはブラウザのsame origin policyとなる。iframeでsandboxの実現、といってもiframeのsandbox属性とはあまり関係ないので注意。

iframeそのものはほぼすべてのブラウザで利用可能であるため使い勝手がよい。そしてiframeのURLが別ドメインである限りsame origin policyが確実にフレーム外のリソースへのアクセスを遮断してくれるため、どんなコードが入ってもほぼ問題ない（ブラウザクラッシュくらいはできるだろうが）。そしてモダンブラウザであればpostMessageによってフレーム間のメッセージによる通信は簡単に実現でき、かつこのメッセージのやり取りは完全にコンテナ側で制御可能である。

もちろんiframeだからといってUIを矩形フレーム内にレンダリングする必要はない。親フレームのDOMツリーにコンポーネントのUIを差し込み、その制御やイベントハンドルは隠れたiframe内のスクリプトで実行するといったことは十分可能である。

問題としては、iframeはそもそもメモリ食いであり初期化も時間がかかるためページ内のコンポーネントが細かくなりすぎると利用するのがつらいという点と、非同期のpostMessageを介するため頻繁に更新される同期的な制御が必要な場合にはあまり向いていないだろうということか。ただ、これらもiframeインスタンスのプーリングや、Virtual DOM的な非同期での差分伝播といったことで工夫は可能かもしれない。

上記の2つ以外のアプローチによるSandbox化はおそらく何らかのバイパス可能な穴が存在する。たとえばwindow/documentなどを上書きしたコンテキストでスクリプトを実行するというアプローチは、数々の方法によって元のオブジェクトへの参照が取得できることが[知られており](http://stackoverflow.com/questions/3277182/how-to-get-the-global-object-in-javascript)、完全なSandboxとしては動作しない。

なお、コンポーネントのDOMの隔離のためにブラウザが用意した仕組みとして[Shadow DOM](https://www.w3.org/TR/shadow-dom/)と呼ばれるものがあるが、これはDOM生成やスタイルシートの適用などは分離できるが、残念ながらスクリプト実行のSandbox化はこれだけでは実現できない。

## LockerServiceの「Sandbox」

LockerServiceでは[iframeは使わない](https://developer.salesforce.com/blogs/developer-relations/2016/04/introducing-lockerservice-lightning-components.html)としている。

> **Does Locker use an iFrame for sandboxing**
> ​No​ – your code runs directly in the same browser window as the rest of the components in an application.

iframeを利用しない場合はCaja的なアプローチでJSを静的解析して変換する以外にはなくなるはずだが、静的リソースからスクリプトロードされるコードについても適用されなければその意味はなくなる。Componentからltng:requireを使った時だけ変換するのかもしれないけれど、さて本当にやれるのだろうか。Cajaですらいろいろ問題に直面し普及できなかったのに、[コンポーネントのJSに正規表現リテラルがはいっているだけでエラーになる](http://qiita.com/twk/items/60185b2ea541190bc7ec)ようなお粗末なことが発生する環境では少し荷が重いのではないか。

ここで考え方を全く変えて、完全にセキュアなSandboxの実現は目指さないで、スクリプトが隔離された仮想環境でもシームレスに動作することだけを目指す、としてみる。この場合、お行儀の悪いものを完全にシャットアウトして動作させない、ということは諦めるが、その検知くらいはできるかもしれない。これができれば審査の負荷を軽減するためのスクリーニングとしては有効な方法だろう。ただ、結局のところ検知を行うにも動的環境での実行結果から得られるものにならざるをえず、漏れは避けられない。審査による人手の判断が砦になっているのに、それを果たしてSandboxと呼べるか定かでない。

## 所感

殊セキュリティについて、不完全なものに対して完全であるといった印象をあたえることがあってはならないし、また不完全でありながらさらにどこにも実益がないのであれば、そのために開発者が無用な困難を強いられることなど論外である。

## 追記(2016/4/19)

Sandboxの実現として、iframe利用とほぼ同質のものであるが、[Web Workers](https://developer.mozilla.org/ja/docs/Web/Guide/Performance/Using_web_workers)を使うというやり方もある。

こちらは実質的にはDOMがないiframeとして扱える上に、生成はiframeに比べれば確実に軽量。またスクリプト提供のURLを別ドメインに分ければiframeと同様にSame Origin Policyが適用されるため分離についても問題ない。

ただ、DOMが存在しないので、通常のスクリプトをそのまま使えるようにしようと思ったら、確実に何らかの仮想的なDOMの実装（Virtual DOMというとReactとかのそれと混乱しそう）が必要となってくる。これがLockerServiceのSecureElementの役割になる、ということかもしれない。

元ページのDOMを直接いじることはできずpostMessage()でのメッセージを介すしかないのは異なるドメインのiframeの場合と同様。よって非同期でのDOMツリー同期というまあまあ難しそうな課題が発生するが、不可能な話とまではいかない。ここを彼らががんばった（＆満足行くレベルで互換性がある）のであれば評価に値する。