ここでの回答の大部分は、electronアプリに大きなセキュリティホールを残すため、この回答が注目を集めることを願っています。実際、この答えは本質的require()
に、electronアプリで使用するためにすべきことです。(v7ではそれを少しすっきりさせる新しいElectron APIがあります)。
私はgithubで詳細な説明/解決策を、最新の電子APIを使用してどのようにできるrequire()
かを書きましたが、ここでは、プリロードスクリプト、contextBridge、およびipcを使用するアプローチに従う必要がある理由を簡単に説明します。
問題
Electronアプリはnodeを使用できるので素晴らしいですが、この力は両刃の剣です。注意しないと、誰かがアプリを介してノードにアクセスできるようになります。ノードを使用すると、悪意のあるアクターがマシンを破損したり、オペレーティングシステムファイルを削除したりする可能性があります(とりわけ)。
コメントで@raddevusによって取り上げられたように、これはリモートコンテンツをロードするときに必要です。electronアプリが完全にオフライン / ローカルである場合は、おそらく単純に有効にするだけです。ただし、アプリを使用する偶発的/悪意のあるユーザーに対する保護手段として引き続き機能し、マシンにインストールされる可能性のあるマルウェアが電子アプリと相互作用して攻撃ベクトルを使用しないようにすることを選択します(非常にまれです) 、しかし起こる可能性があります)!nodeIntegration:true
nodeIntegration:false
nodeIntegration:true
問題はどのように見えますか
この問題は、次の場合に発生します(以下のいずれか):
- き
nodeIntegration:true
有効
remote
モジュールを使用する
これらの問題はすべて、レンダラープロセスからノードへの中断のないアクセスを提供します。レンダラープロセスが乗っ取られた場合、すべてが失われたと見なすことができます。
私たちのソリューションは何ですか
解決策は、レンダラーにノード(つまり)への直接アクセスを許可するのrequire()
ではなく、electronメインプロセスにへのアクセスを許可しrequire
、レンダラープロセスがを使用する必要があるときはいつでもrequire
、メインプロセスへのリクエストをマーシャリングすることです。
Electronの最新バージョン(7+)でこれが機能する方法は、レンダラー側でipcRendererバインディングをセットアップし、メイン側でipcMainバインディングをセットアップします。ipcMainバインディングでは、私たちが使用するモジュールを使用するリスナーメソッドを設定しますrequire()
。私たちの主なプロセスはrequire
それが望むすべてを行うことができるので、これはうまくいきます。
contextBridgeを使用してipcRendererバインディングをアプリコード(使用する)に渡すため、アプリがrequire
メインでdモジュールを使用する必要がある場合、IPC(プロセス間通信)を介してメッセージを送信し、メインプロセスが実行されますコードをいくつか入力し、結果をメッセージで送り返します。
大まかに、ここにあなたがしたいのです。
main.js
const {
app,
BrowserWindow,
ipcMain
} = require("electron");
const path = require("path");
const fs = require("fs");
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
async function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false, // is default value after Electron v5
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
preload: path.join(__dirname, "preload.js") // use a preload script
}
});
// Load app
win.loadFile(path.join(__dirname, "dist/index.html"));
// rest of code..
}
app.on("ready", createWindow);
ipcMain.on("toMain", (event, args) => {
fs.readFile("path/to/file", (error, data) => {
// Do something with file contents
// Send result back to renderer process
win.webContents.send("fromMain", responseObj);
});
});
preload.js
const {
contextBridge,
ipcRenderer
} = require("electron");
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
// whitelist channels
let validChannels = ["toMain"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
}
);
index.html
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8"/>
<title>Title</title>
</head>
<body>
<script>
window.api.receive("fromMain", (data) => {
console.log(`Received ${data} from main process`);
});
window.api.send("toMain", "some data");
</script>
</body>
</html>
免責事項
私は、secure-electron-template
electronアプリを構築するための安全なテンプレートの作成者です。私はこのトピックに関心があり、数週間(この時点で)これに取り組んできました。