1 ポイント 投稿者 GN⁺ 2024-07-08 | 1件のコメント | WhatsAppで共有
  • @sebastianwessel/quickjs は、QuickJSエンジンをWebAssemblyにコンパイルしたサンドボックス内で JavaScriptとTypeScriptコード を安全に実行するためのTypeScriptパッケージ
  • 信頼できないコードを隔離して実行することに重点を置いており、QuickJS WebAssemblyサンドボックス を通じて軽量で高速な実行環境を提供
  • 標準のNode.jsモジュール対応、仮想ファイルシステム のマウント、カスタムNodeモジュールのマウント、fetch クライアントの提供、テストランナーとChaiベースの expect を含む
  • 使用例では loadQuickJs() でQuickJS wasmを一度読み込んだあと、runSandboxed()evalCode() でコードを実行し、allowFetchallowFsenv などのオプションを渡す
  • TypeScriptアプリケーションで外部JavaScriptコードを実行する必要がある開発者は、隔離、性能、APIのシンプルさをあわせて考慮した実行環境として活用できる

QuickJS WebAssemblyサンドボックスパッケージ

  • @sebastianwessel/quickjs は、JavaScriptとTypeScriptコード をWebAssemblyサンドボックス内で実行できるようにするTypeScriptパッケージ
  • QuickJSエンジンをWebAssemblyにコンパイルして使用し、信頼できないコードを 隔離された環境 で実行する用途に適している
  • このパッケージは、軽量で高速なQuickJSエンジンを活用して、コード実行のための堅牢な環境を提供
  • 完全なドキュメント、サンプル、オンラインプレイグラウンドを提供

提供機能

  • 安全な実行: 信頼できないJavaScriptおよびTypeScriptコードを、安全で隔離された環境で実行
  • 標準Node.jsモジュール: 一般的なユースケース向けに、基本的な標準Node.jsモジュールのサポートを提供
  • ファイルシステム: 仮想ファイルシステムをマウント可能
  • カスタムNodeモジュール: ユーザー定義のNodeモジュールをマウント可能
  • ネットワーク呼び出し: fetch クライアントを提供し、http(s)呼び出しを実行可能
  • テスト対応: テストランナーとChaiベースの expect を含む
  • 性能と統合性: QuickJSエンジンの軽量性と効率性を活かし、既存のTypeScriptプロジェクトに容易に統合できる
  • シンプルなAPI: サンドボックス内でJavaScriptとTypeScriptコードを実行・管理するためのユーザーフレンドリーなAPIを提供

基本的な利用フロー

  • まず @jitl/quickjs-ng-wasmfile-release-sync から variant を取得し、@sebastianwessel/quickjs から loadQuickJsSandboxOptions を使用する
  • loadQuickJs(variant) はQuickJS wasmを読み込んで初期化するが、この処理は リソース集約的 であるため、可能であれば一度だけ実行する方法が推奨される
  • loadQuickJs()runSandboxed を返し、その後のサンドボックス実行に使われる
  • SandboxOptions の例には次のオプションが含まれる
    • allowFetch: true: コードに fetch を注入し、データ取得を許可
    • allowFs: true: 仮想ファイルシステムをマウントし、node:fs モジュールを提供
    • env: サンドボックスコード内で使用する環境変数の値を渡す

実行例で可能な動作

  • サンドボックス内のコードは path から join をimportし、join('src','dist') を呼び出してホストシステムのログに src/dist を出力
  • env.MY_ENV_VAR を読み取り、ホストシステムのログに "env var value" を出力
  • new URL('https://example.com') でURLを作成し、fetch(url) を呼び出したあと f.text() の結果を返す
  • コードは runSandboxed(async ({ evalCode }) => evalCode(code), options) の形で実行
  • 結果の例は { ok: true, data: '<!doctype html>\n<html>\n[....]</html>\n' } の形式

ライセンスと適した用途

  • このプロジェクトは MIT License で提供される
  • TypeScriptアプリケーション内でJavaScriptコードを安全に実行したい開発者に適している
  • QuickJS WebAssemblyサンドボックスを通じて、性能と安全性 を両立する実行環境を提供

1件のコメント

 
GN⁺ 2024-07-08
Hacker News の意見
  • quickjs-emscripten ランタイムライブラリの作者として、quickjs-emscripten 向けに作られた、人間工学的な標準ライブラリ風の設計が気に入っている。
    ブラウザやバンドラーで動かしてみたのか気になる。バリアント名を文字列として受け取り、import(variantName) に動的に渡す方式は、Webpack のようなツールとは相性がよくない可能性がある。
    セキュリティ警告: このライブラリは、ゲスト側の信頼できないコードが、ホストの fetch 関数と同じ Cookie で fetch を呼び出せるようにする。fetch を有効にして信頼できないコードを実行してはいけない。
    サンドボックス内のコードが、ホストコンテキストで認証された任意の HTTP API を呼び出せるなら、それは「サンドボックス」ではない。
    quickjs-emscripten が低レベルで、魔法のような機能を避けている理由は、提供する API が安全だと自信を持って言うためだ。魔法のようなシリアライズや、簡単なネットワーク/ファイルシステムアクセス機能の要望はおおむね断っている。こうしたコードはセキュリティ上のミスが起きやすい領域だからだ。
    信頼できないコードを実行するときは、サンドボックス自体だけでなく、サンドボックスに公開する API のために書いたコードまで慎重に監査する必要がある。
    別の HN ユーザーが Fetch の Cookie について質問したコメントを見て、潜在的なセキュリティ問題に気づいた。
    参考資料: Figma プラグインのサンドボックスセキュリティに関する記事 https://www.figma.com/blog/how-we-built-the-figma-plugin-sys...https://www.figma.com/blog/an-update-on-plugin-security/、Quickjs-emscripten README https://github.com/justjake/quickjs-emscripten

    • iframe を使えば、このライブラリを使うかどうかに関係なく、fetch の Cookie 問題を防げるのか気になる。あるいは iframe でも依然として問題になるのか知りたい。
  • サーバー側とブラウザ側のどちらでも JavaScript をサンドボックス化する方法は多いが、DOM アクセスを「サンドボックス化」する方法があるのかは分からない。
    例えば、信頼できないサードパーティには、あらかじめ決めた位置の DOM 要素だけへのアクセスを与える、といった形だ。自分の知る限り、これを可能にする唯一の技術は iframe だが、残念ながら重くて遅い。
    プラグインをホストできるアプリを作っているのだが、プラグインに DOM アクセスを与えると、文字どおり何でもできるようになってしまうように思える。

    • Salesforce は Web Components とパッチ済みの ShadowRoot を組み合わせてこれに対処している。シャドウルートへの参照を持つコードがドキュメントの残りの領域へ出ていけないようにし、SES(Secure EcmaScript)に関連する安全な評価関数で、信頼できないスクリプトがアクセスできるグローバルオブジェクトを制限している。
      その安全な評価関数はかなりすごい。核心はおそらくここだと思う: https://github.com/Agoric/realms-shim/blob/v1.1.0/src/evalua...
      プラットフォームレベルでこの問題を解くための分離された Web Componentsというアイデアもある: https://github.com/WICG/webcomponents/issues/1002
    • 最も近いものは Allen Wirfs-Brock の jsmirrors プロトタイプだが、DOM については仕様化まで至らず、そもそもそこまで意図していたわけでもなかったと理解している。JavaScript というプログラミングシステムのための**ケイパビリティ(capability)**だけを扱っていた。
      jsmirrors に着想を得て「dommirrors」のようなものを自分で試すことはできるが、大仕事になる。jsmirrors をそのまま使って、望むことをある程度達成する回避策もあるが、使いやすくはない。
      ただし、DOM アクセスを仲介したりシミュレートして与えたりすることは、ほぼ確実に本当に望んでいるものではない。相手に実際に何を許可したいのかを把握し、その動作を可能にするケイパビリティだけを与えるべきだ。
      例えば、どこかにボタンを追加させたいなら、親要素というアンカーポイントを渡し、document.createElement で DOM ノードを作らせるべきだと考えるかもしれない。だが実際に欲しいのは、彼らに document.createElement へのアクセス権を与えることではなく、add-button ケイパビリティだ。だから addButton を実装すればよい。
      さらに読む: <https://news.ycombinator.com/item?id=30703531#30706060>
      CSP がこの用途のものだと言う人の話は聞かないほうがいい。そうではない。正確に言えば、CSP は本来の目的に対しても設計が悪く、ユーザーに敵対的な代物なので、現在のように実装・拡張されるべきではなかったと思う。これに依存するのは危険だ。
    • 本当に安全にアクセスさせるには、サードパーティコードに別ドメインの iframeを与え、sandbox 属性を設定する方法しかない。
      親ページ側で DOM コンテンツのサイズを測定し、一定の範囲内で iframe のサイズを調整できるので、アプリの UI により自然に統合することはできる。
      ユーザー間の露出レベルと信頼レベルに応じて、iframe 内でのなりすまし/フィッシングやクリックジャッキングの試みには注意が必要だ。理想的には、フレームが Web リクエストを一切行えないようにロックすべきで、これは画像の読み込みもできないという意味だ。
      そうすれば、例えばユーザーが偽のパスワードフォームにパスワードを入力するようだまされたとしても、フレーム外へデータを流出させる方法はない。
      iframe がリクエストできるリソースの種類を制限する主な方法は Content-Security-Policy であり、これによってサードパーティ画像やスクリプトなどをすべて無効にできる。
      https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameE...
      https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
      ほかの sandbox 属性も有効にし、Web カメラのようなプライバシーに敏感な DOM API へのアクセスは無効にすべきだ。
      https://developer.mozilla.org/en-US/docs/Web/HTML/Element/if...
      https://developer.mozilla.org/en-US/docs/Web/Security/IFrame...
      https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Pe...
    • 可能な方法としては、サードパーティがレンダリングしたい HTML を文字列として送る APIを追加し、それを各種ライブラリのいずれかでパースしたうえで、パース済みデータに基づいて DOM を作り直す、というやり方になりそうだ。
      こうすれば、許可する HTML 要素と属性をホワイトリストで制限できる。ネイティブ DOM イベントの受信まで許可しようとすると複雑になるが、不可能ではない。
      イベント名の文字列と、イベントを受け取る要素 id を受け取る API のようなものが必要で、実際の DOM でそのイベントを検知した後、使用中の JavaScript サンドボックス内へ同じイベントを複製する必要がある。そのサンドボックス内では、前述の API にアクセスできなければならない。
    • 思いつきでは、次のようにできるかもしれない。バックエンドでサードパーティコードをリクエストし、そのコードとハッシュ列を結び付ける。
      バックエンドで HTML を動的に変更し、id 値がハッシュ列である div タグを挿入する。さらに HTML を変更して、自分のドメインからサードパーティコードをリクエストする script タグを挿入し、追跡用にその script タグの data 属性へハッシュ値を追加する。

バックエンドでサードパーティのコードを修正し、document.window. のすべてのインスタンスを document.getElementById(hash_value). に置き換え、すべてのクエリセレクターが #hash_value で始まるようにする
Element プロトタイプの .parentNode をカスタムプロパティに置き換え、提供されたコンテナの外へ出ようとする試みを検査してブロックする必要がある
その後、HTML ドキュメントをブラウザに送る。サードパーティのコードが壊れても構わない。制約はサードパーティに知らせるべきで、サーバーに送る前に自分たちのコードをテストするのは彼らがやるべきこと
重要なのは、コードが動的に提供されたコンテナの外へ出られないようにすることだけ。セキュリティ違反を見つけるため、この部分は定期的にテストする必要がある
実際には動かないかもしれないが、試してみると面白そう

  • 偶然、先週 QuickJS を試してみて、結局 isolated-vm に落ち着いた。どちらもセキュリティ要件は満たしていたが、設定、破棄、eval 実行のオーバーヘッドの面で isolated-vm のほうがはるかに性能が良かった

  • 興味深いアプローチだ。ワーカーで隔離し、JavaScript 環境を整理する手法を使う別の JavaScript サンドボックスライブラリ[1]の作者としては、JavaScript を解釈する方式、つまり JavaScript 内の JavaScript や、この場合のような WASM 内の JavaScript が、最も高いレベルの隔離を提供すると考えている
    また、ホスト側の JavaScript 仮想マシン自体のバグに直接さらされることもない。Node を対象にするなら、これはより重要かもしれない。最近の一部の流れを除けば、Node.js は Deno と違って 隔離とサンドボックス化 を念頭に置いて設計されたようには見えないからだ
    API を見ると、createRuntimefetch 以外にホスト環境の呼び出しを定義できるのかは分からない。この機能はかなり有用なはずだ。外部世界との通信をすべて許可するかすべて遮断するかではなく、制御された形で制限できるからだ
    同様に、ブラウザもサポートしていないように見える。少なくとも esm.sh で簡単に確認した限りではそうだった。これも有用な機能になり得る
    オーバーヘッドがどの程度なのか気になるので試してみるつもりだし、先に述べたようにアプローチ自体はかなり堅実に見える
    [1] @exact-realty/lot

  • CPU が速くなりすぎたので、今度は インタプリタの中でインタプリタ を動かそうという話に見える

  • QuickJS で JavaScript を実行する利点として 性能 は挙げない。QuickJS はホスト側の JavaScript 仮想マシンとはまったく勝負にならない
    ただし、古い C インタプリタや JavaScript で実装したインタプリタよりは速い可能性がある

    • スクリプト実行時間より Node.js プロセスの起動時間が支配的なら、性能上の利点があるかもしれない。おそらく多くの サーバーレス関数 型のスクリプトがこれに当てはまりそうだ
  • これでユーザーが提供した JavaScript コードを実行できそうだ。ユーザーの TypeScript コードをサンドボックス環境でバンドラーにかける方法を探していた
    QuickJS 内で webpack のような バンドラー を実行する方法について、おすすめがあるか気になる

    • QJS でやる方法は分からないが、ブラウザでバンドラーを実行したいなら WebContainers がまさにその用途向けに作られているようだ
      [1]: https://webcontainers.io/
  • とてもクールだ。WASM にコンパイルされているなら、ブラウザで実行 できるのか気になる。可能で、しかもリクエストに Cookie を付けずに fetch リクエストを送れるなら面白そうだ

  • 以前の職場では Quickjs-emscripten で segmentation fault と無言のエラーがあまりに多く、プロジェクトが保留になった
    もう一度やるなら、より多くのプログラムで正しく動作し、公式に承認された WASM バンドルがあるエンジンを使うと思う

  • iframe でコードを安全にサンドボックス化できると思っていたが、確信はない。もちろん独自のインタプリタを使えば、より細かい時間制限やカスタム API といった機能をもっと入れられそうだ

    • 自分も、特に サービスワーカー まで混ぜる場合、この点をもっと知りたい
      今は window message でコードを受け取る iframe を使っていて、入力コードを評価したあと window message で応答を返せるので、かなりうまく動いている。ただし、サンドボックスの外へ抜け出す穴があるかどうかは確信がない
      もっと複雑な場合、たとえばパッケージ依存関係は Babel でパースしてから CDN(esm.sh)を使う import map を作ろうとしたが、CDN 化されたバージョンがうまく動かないことがあった
      そこで StackBlitz を使ったところ、かなりうまく動くが、セキュアコンテキストではない環境では少し問題がある
      結局、コードと依存関係(package.json)を受け取り、Docker コンテナ内で Vite ビルドを実行して結果を返す小さな Web サーバーを作った。それなりにうまく動くが、ときどき遅い
      完全にクライアント側でビルドできるとよく、StackBlitz はおおよそそういうことをしている
    • iframe には 権限が過剰に付与 されている。たとえば iframe はデータをサードパーティへ流出させられる