ES2015 Proxyを活用して楽しみながら成果を上げる
Alon Niv
2016年8月23日
0 分で読めますES2015については多くのことがこれまで書かれてきました。アロー関数やスコープ付き変数宣言、賛否の分かれるクラスなどが取り上げられています。しかし、これまであまり注目されてこなかった機能があります。それがProxyです。JS開発者は、コードベース全体でトラップ機構に頼ることに慣れていませんが、これは非常に役立つ用途がいくつもあります。いくつか挙げると、私たちの
テスト、モック、モンキーパッチ
ObserverおよびVisitorデザインパターン複雑な概念を抽象化する
これまで、このような仕組みは言語に用意されていませんでした。Proxyはこの問題を解決しつつ、私たちが慣れ親しみ、愛用しているJSらしさを保っていると思います(オブジェクトに[Symbol.__setattr__]メソッドを追加する必要はありません)。この記事では、よく使われるトラップ(get、set、has、deleteProperty)の例を紹介します。Snykらしく、依存関係を題材にします。
目標
開発者がモジュールへのアクセスを特定のものに制限し、安全にrequireできるパッケージを作るのが目標です。結局のところ、関数型ユーティリティモジュールにfsを使わせるわけにはいきませんよね?
準備
Proxyコンストラクターは、2つのパラメーターを受け取ります。
target:プロキシの対象となるオブジェクトhandler:処理するトラップの仕様を含むオブジェクトです(ここでいうトラップとは、プロパティへのアクセス、設定、削除など、プロキシに特定のイベントが発生したときに呼び出される関数です)。例を見ていきましょう。
requireの仕組み
この部分を説明するのは、nodeのドキュメントがとても分かりやすく解説しているからというだけではありません。これからモジュールキャッシュを巧みに操作するので、requireの流れを明確にしておきたいのです。
モジュール
xがモジュールyをrequireするyという名前が絶対パスに解決される(fsなどのコアモジュールの場合は除く)解決された名前を、存在確認をせずにキャッシュから取得する
取得した値が定義済み(Moduleオブジェクト)の場合、エクスポートされた値を返す
それ以外の場合、nodeは実際にファイルシステムからファイルを取得し、Moduleオブジェクトにコンパイルします。オブジェクトはキャッシュに格納され、エクスポートされた値が返されます。
このキャッシュはグローバルなシングルトンで、require('module')._cacheおよびrequire.cacheからアクセスできる、通常のJSオブジェクトです。
では、コードを書いてみましょう
簡単にするため、requireされたモジュールで使わせたくないモジュールをブラックリストに登録するインターフェースを用意するとしましょう。次のようになります。
では、完成形のイメージがつかめたところで、コードを書き始めましょう。
これで完了ですね?いえ、そう簡単ではありません。悪意のあるモジュールがブラックリスト対象のモジュールをrequireできないようにはしましたが、ほかにも厄介な手口があります。
補足:require.cacheについて:モジュールキャッシュ内の単一のモジュールを改変したい場合は、require.cacheを使います。一方、キャッシュ機構全体を差し替えたい場合は、require('module')._cache = ...を使います。この状況にも対処できるよう、コードを修正しましょう。
さて……これで完了……でしょうか?いいえ。次の悪意あるコードを考えてみましょう。
では、最後の仕上げを加えましょう。
これで完了です。少なくとも、そう思います。
このプロキシを回避する方法を思いつきましたか?Twitterで@snyksecまでぜひ教えてください!
このPOCのリポジトリはこちらで公開されています。
Capture the Flagを始めよう
オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。


