WebWorkerはJavaScriptサンドボックスの詳細をカプセル化します

WebWorkerはJavaScriptサンドボックスの詳細をカプセル化します

1. シナリオ

前回の記事では、Quickjs が JavaScript サンドボックスの詳細をカプセル化し、 quickjsに基づいてサンドボックスが実装されました。ここでは、Web ワーカーに基づいて代替ソリューションが実装されています。 web worker何であるか分からない場合、または一度も調べたことがない場合は、 Web Workers API確認してください。つまり、これはブラウザに実装されたマルチスレッドであり、別のスレッドでコードを実行し、そのコードと通信する機能を提供します。

2. IJavaScriptShadowboxを実装する

実際、Web Worker はpostMessage/onmessageというevent emitter API を提供しているため、実装は非常に簡単です。

実装は 2 つの部分に分かれており、1 つはメイン スレッドでIJavaScriptShadowboxを実装すること、もう 1 つはweb workerスレッドでIEventEmitter実装することです。

2.1 メインスレッドの実装

「./IJavaScriptShadowbox」から {IJavaScriptShadowbox} をインポートします。

エクスポートクラス WebWorkerShadowbox は IJavaScriptShadowbox を実装します {
  破棄(): void {
    this.worker.terminate();
  }

  民間労働者!:労働者;
  eval(コード: 文字列): void {
    const blob = new Blob([code], { type: "application/javascript" });
    this.worker = 新しいWorker(URL.createObjectURL(blob), {
      資格情報: "include",
    });
    this.worker.addEventListener("メッセージ", (ev) => {
      const msg = ev.data as { チャネル: 文字列; データ: 任意 };
      // console.log('msg.data: ', msg)
      if (!this.listenerMap.has(msg.channel)) {
        戻る;
      }
      this.listenerMap.get(msg.channel)!.forEach((handle) => {
        ハンドル(msg.data);
      });
    });
  }

  プライベート読み取り専用リスナーマップ = 新しい Map<string, ((data: any) => void)[]>();
  出力(チャンネル: 文字列、データ: 任意): void {
    this.worker.postMessage({
      チャンネル: チャンネル、
      データ、
    });
  }
  on(チャンネル: 文字列、ハンドル: (データ: 任意) => void): void {
    if (!this.listenerMap.has(channel)) {
      this.listenerMap.set(チャンネル、[]);
    }
    this.listenerMap.get(チャンネル)!.push(ハンドル);
  }
  offByChannel(チャンネル: 文字列): void {
    this.listenerMap.delete(チャンネル);
  }
}

2.2 Webワーカースレッドの実装

「./IEventEmitter」から IEventEmitter をインポートします。

エクスポートクラスWebWorkerEventEmitterはIEventEmitterを実装します{
  プライベート読み取り専用リスナーマップ = 新しい Map<string, ((data: any) => void)[]>();

  出力(チャンネル: 文字列、データ: 任意): void {
    postMessage({
      チャンネル: チャンネル、
      データ、
    });
  }

  on(チャンネル: 文字列、ハンドル: (データ: 任意) => void): void {
    if (!this.listenerMap.has(channel)) {
      this.listenerMap.set(チャンネル、[]);
    }
    this.listenerMap.get(チャンネル)!.push(ハンドル);
  }

  offByChannel(チャンネル: 文字列): void {
    this.listenerMap.delete(チャンネル);
  }

  初期化() {
    onmessage = (ev) => {
      const msg = ev.data as { チャネル: 文字列; データ: 任意 };
      if (!this.listenerMap.has(msg.channel)) {
        戻る;
      }
      this.listenerMap.get(msg.channel)!.forEach((handle) => {
        ハンドル(msg.data);
      });
    };
  }

  破壊する() {
    このリスナーマップをクリアします。
    onmessage = null;
  }
}

3. WebWorkerShadowbox/WebWorkerEventEmitterを使用する

メインスレッドコード

const シャドウボックス: IJavaScriptShadowbox = new WebWorkerShadowbox();
shadowbox.on("hello", (名前: 文字列) => {
  console.log(`hello ${name}`);
});
// ここでのコードは、shadowbox.eval(code); の下の Web ワーカー スレッドのコードを参照します。
shadowbox.emit("open");


Web ワーカー スレッド コード

const em = 新しい WebWorkerEventEmitter();
em.on("open", () => em.emit("hello", "liuli"));


以下はコード実行フローの概略図です。Web web workerサンドボックス実装では、サンプル コード実行フローが使用されます。

4. WebワーカーのグローバルAPIを制限する

JackWoeker指摘したように、 web workerは安全でないAPIが多数あるため、以下のAPIを含むがこれらに限定されないAPIを制限する必要がある。

  • fetch
  • indexedDB
  • performance

実際、 web workerにはデフォルトで276グローバル API が付属しており、これは私たちが考えているよりもはるかに多い可能性があります。

performance/SharedArrayBuffer apiを介して Web 上でサイドチャネル攻撃を実行する方法を説明した記事があります。SharedArrayBuffer在SharedArrayBuffer api現在ブラウザーでデフォルトで無効になっていますが、他の方法があるかどうかは誰にもわかりません。したがって、最も安全な方法は、API ホワイトリストを設定してから、ホワイトリストに登録されていない API を削除することです。

// ホワイトリストWorkerGlobalScope.ts
/**
 * Webワーカーランタイムのホワイトリストを設定して、安全でないAPIをすべて禁止する
 */
エクスポート関数 whitelistWorkerGlobalScope(list: PropertyKey[]) {
  const ホワイトリスト = 新しい Set(リスト);
  const all = Reflect.ownKeys(globalThis);
  すべて.forEach((k) => {
    if (ホワイトリスト.has(k)) {
      戻る;
    }
    if (k === "ウィンドウ") {
      console.log("ウィンドウ: ", k);
    }
    Reflect.deleteProperty(globalThis, k);
  });
}

/**
 * グローバル値のホワイトリスト */
定数ホワイトリスト: (
  | キーof タイプof グローバル
  | WindowOrWorkerGlobalScope のキー
  | 「コンソール」
)[] = [
  "グローバルこれ",
  "コンソール"、
  "タイムアウトの設定",
  「タイムアウトをクリア」、
  "setInterval"、
  「クリア間隔」、
  「ポストメッセージ」、
  "オンメッセージ",
  "反映する"、
  "配列"、
  "地図"、
  "セット"、
  "関数"、
  "物体"、
  「ブール値」、
  "弦"、
  "番号"、
  "数学"、
  "日付"、
  「JSON」、
];

ホワイトリストWorkerGlobalScope(ホワイトリスト);

次に、サードパーティのコードを実行する前に上記のコードを実行します。

「./whitelistWorkerGlobalScope.js?raw」からbeforeCodeをインポートします。

エクスポートクラス WebWorkerShadowbox は IJavaScriptShadowbox を実装します {
  破棄(): void {
    this.worker.terminate();
  }

  民間労働者!:労働者;
  eval(コード: 文字列): void {
    // この行がキーです const blob = new Blob([beforeCode + "\n" + code], {
      タイプ: "application/javascript",
    });
    // その他のコード。 。 。
  }
}

ソース コードの記述には ts を使用するため、 ts をjs bundleにパッケージ化し、それをviteの ? rawを通じて文字列としてインポートする必要もあります。以下では、これを行うための簡単なプラグインを作成しました。

「vite」からdefineConfigとPluginをインポートします。
「@vitejs/plugin-react-refresh」から reactRefresh をインポートします。
「vite-plugin-checker」からチェッカーをインポートします。
「esbuild」から{build}をインポートします。
"path" から * をパスとしてインポートします。

エクスポート関数buildScript(scriptList: string[]): プラグイン{
  _scriptList を scriptList.map((src) => path.resolve(src));
  非同期関数buildScript(src: 文字列) {
    ビルドを待つ({
      エントリポイント: [src],
      出力ファイル: src.slice(0, src.length - 2) + "js",
      フォーマット: "iife",
      バンドル: true、
      プラットフォーム:「ブラウザ」、
      ソースマップ: "インライン",
      上書きを許可する: true、
    });
    console.log("ビルドが完了しました: ", path.relative(path.resolve(), src));
  }
  戻る {
    名前: "vite-plugin-build-script",

    非同期configureServer(サーバー) {
      server.watcher.add(_scriptList);
      _scriptList を新しい Set に追加します。
      server.watcher.on("change", (filePath) => {
        // console.log('変更: ', ファイルパス)
        スクリプトセットにファイルパスがある場合
          ビルドスクリプト(ファイルパス);
        }
      });
    },
    非同期ビルド開始() {
      // console.log('buildStart: ', this.meta.watchMode)
      if (this.meta.watchMode) {
        _scriptList.forEach((src) => this.addWatchFile(src));
      }
      Promise.all(_scriptList.map(buildScript)) を待機します。
    },
  };
}

// https://vitejs.dev/config/
デフォルトのdefineConfigをエクスポートする({
  プラグイン: [
    反応リフレッシュ()、
    チェッカー({typescript: true})、
    ビルドスクリプト([path.resolve("src/utils/app/whitelistWorkerGlobalScope.ts")]),
  ]、
});

これで、 web worker内のグローバル API はホワイトリストにあるものだけであることがわかります。

5. Webワーカーサンドボックスの主な利点

chrome devtoolを使用して直接デバッグし、 console/setTimeout/setInterval api
をサポートできます。 console/setTimeout/setInterval api
メッセージ通信を直接サポートするapi

WebWorker カプセル化 JavaScript サンドボックスの詳細に関するこの記事はこれで終わりです。WebWorker カプセル化 JavaScript サンドボックスに関する関連コンテンツの詳細については、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Quickjs は JavaScript サンドボックスの詳細をカプセル化します
  • JavaScript サンドボックスの探索
  • JavaScript Sandboxについての簡単な説明
  • フロントエンドJSサンドボックスを実装するいくつかの方法についての簡単な説明
  • Node.jsサンドボックス環境についての簡単な説明
  • Node.js アプリケーション用の安全なサンドボックス環境の設定
  • JS実装クロージャにおけるサンドボックスモードの例
  • JS サンドボックス モードの例の分析
  • JavaScript デザインパターン セキュリティ サンドボックス モード

<<:  Docker の Windows ストレージ パス設定操作

>>:  10分でCSS3グリッドレイアウトを理解する

推薦する

MySQL 5.x の文字化け問題の解決方法

MySQL はよく使われるオープンソースのデータベース ソフトウェアですが、初めてのユーザーにはあま...

WeChatアプレットがスネークゲームを実装

この記事では、参考までに、スネークゲームを実装するためのWeChatアプレットの具体的なコードを紹介...

プロセスのすべての情報を表示するLinuxメソッドの例

サーバー上にタスク プロセスがあります。 ps -ef | grep task を使用して表示すると...

CSS3+JavaScript を使用したクールな呼吸効果のサンプル コード

CSS3 アニメーションで実現したシンプルでクールな効果。最終的な効果は次のようになります。 ページ...

Linux での rpm、yum、ソースコードの 3 つのインストール方法の詳細な紹介

第1章 ソースコードのインストールRPM パッケージは特定のシステムとプラットフォームに応じて指定さ...

キャンバスでPS消しゴムスクラッチカードの効果を実現するためのJSの使用方法の詳細な説明

目次効果のデモンストレーション:メインJSコード実装 <div class="box...

MySQL テーブルにはどのくらいの量のデータを保存できますか?

プログラマーは MySQL を扱う機会が多く、毎日触れているとも言えますが、MySQL テーブルには...

フレックスマルチカラムレイアウトで発生する問題と解決策の詳細な説明

フレックス レイアウトは間違いなくシンプルで使いやすいです。レイアウトをよりシンプルかつ高速にします...

データベースSQL文の最適化

最適化する理由:実際のプロジェクトが開始され、データベースが一定期間稼働した後、初期のデータベース設...

WeChat 8.0の爆発的な特殊効果を実現するために300行以上のCSSコードが必要

WeChat 8.0 アップデートの主な特徴は、アニメーション絵文字のサポートです。送信するメッセー...

燃える炎効果の英語フォント16種類をシェアする

私たちは視覚の世界に住んでおり、多くの視覚効果に囲まれています。コンピューターの前にいても、屋外にい...

クラウドネイティブテクノロジーKubernetesスケジューリングユニットポッドの使用の詳細な説明

k8s の最小のスケジューリング単位 --- pod前回の記事では、k8s が解決できる問題を簡単に...

JavaScript は最大値と最小値のアルゴリズムを通じて AI 三目並べゲームを実装します

では、早速スクリーンショットを実行してみましょう。黒い駒はプレイヤーの位置、赤い駒はコンピュータの位...

つまり、フィルターコレクション

IE は開発の初期段階では頭を悩ませましたが、他のブラウザとは異なります。他のブラウザがサポートして...