在打包应用程序的本地页面中启用Electron中的nodeIntegration是否安全?

16

当我在关闭nodeIntegration的情况下创建自定义窗口控件(如关闭、最小化/最大化和还原)时,我被卡住了。我在呈现器的本地HTML文件中创建了这些按钮。

main.js

mainWindow = new BrowserWindow({
    x, y, width, height,
    frame: false,
    show: false,
    webPreferences: { devTools: true }
});

mainWindow.loadURL(url.format({
    protocol: 'file:',
    slashes: true,
    pathname: path.join(__dirname, 'assets', 'index.html')
}));

首页.html

<div id='minimize' class='noSelect'>&#xE921;</div>
<div id='maximize' class='noSelect'>&#xE922;</div>
<div id='restore' class='noSelect'>&#xE923;</div>
<div id='close' class='noSelect'>&#xE8BB;</div>

<script type='text/javascript' src='../assets/js/index.js'></script>

默认情况下,nodeIntegration已关闭,因此index.js无法访问Node。但是,我需要能够为按钮添加功能以关闭、最小化/最大化和恢复窗口。

index.js

const { remote } = require('electron');
const mainWindow = remote.getCurrentWindow();

document.getElementById('close').addEventListener('click', () => {
  mainWindow.close();
});
这不起作用因为nodeIntegration被禁用了。在本地页面启用它是否安全?如果不安全,有什么安全的方法可以做到这一点?

这不会起作用,因为nodeIntegration被禁用了。在本地页面启用它是否安全?如果不安全,有什么安全的方法可以实现这个目的?

3个回答

32
TL;DR:仅在从不受信任的来源(例如互联网或用户输入)加载和执行代码时,启用nodeIntegration才会带来风险。如果您完全确认应用程序仅运行您创建的代码(且没有NodeJS模块从互联网加载脚本),则启用nodeIntegration基本上不存在或极低风险。
但是,如果允许用户运行代码(即输入并eval它),或者提供插件API,而您无法控制已加载的插件,则风险级别会上升,因为NodeJS允许任何NodeJS脚本操作文件系统。
另一方面,禁用nodeIntegration后,您将无法与主进程通信或操纵BrowserWindow,因此无法创建自定义窗口控件。但是,您可以使用“preload”脚本文件在完全隔离的渲染器和NodeJS世界之间构建桥梁。这可以通过创建一个脚本文件,然后在创建过程中将该文件作为preload:配置选项传递给BrowserWindow来完成。 Electron文档有一些示例可供参考。另外,熟悉Electron的安全建议也是个好主意。

除了一些来自NPM的模块外,它只会运行我放置的代码。不确定您是否指的是模块和插件。用户不会输入要运行的代码,只会偶尔输入字符串值。 - AfterShotzZHD
1
如果您确信没有任何NPM模块会从互联网加载代码,那么它的风险就很低。 - Alexander Leithner
2
preload脚本的角度怎么样?如果不启用nodeIntegration,那能行吗? - AfterShotzZHD
基本上,是的 - 你可以定义一个函数,窗口控制按钮将使用它,就像预加载示例所演示的那样。 - Alexander Leithner
4
这是一个很好的回答。如果想要查看在禁用nodeIntegration的情况下使用IPC的示例,请参阅https://dev59.com/91QK5IYBdhLWcg3wN9VV#57656281 - Luke H
显示剩余2条评论

9

请注意,2021年起在渲染进程中,您不需要使用nodeIntegration来与主进程进行通信。

相反,您可以使用消息传递来实现,例如:
main.js

const {app, BrowserWindow, ipcMain} = require("electron");
const path = require("path");

app.whenReady().then(open_window);

function open_window() {
  // Explain: Create app window.
  const appWin = new BrowserWindow({
    width: 800,
    height: 600,
    opacity: 0.5,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
    },

  // Explain: Render the app.
  void minisWindow.loadFile("index.html");
  
  // Spec: User can hide App window by clicking the button.
  ipcMain.on("hide-me", () => appWin.minimize());
  
  // Spec-start: When User focuses App window - it becomes fully opaque.
  ipcMain.on("make-window-opaque", () => appWin.setOpacity(1));
  appWin.on("show", () => minisWindow.setOpacity(1));
  appWin.on("blur", () => minisWindow.setOpacity(0.5));
  // Spec-end.
}

preload.js

const {ipcRenderer} = require("electron");

// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.addEventListener("DOMContentLoaded", () => {
  // Spec: User can hide App window by clicking the button.
  document.querySelector("#hideBtn").addEventListener("click", 
    () => ipcRenderer.send("hide-me"));
  });

  // Spec: When User focuses App window - it becomes fully opaque.
  document.body.addEventListener("click", () => ipcRenderer.send("make-window-opaque"));
});

这个例子展示了两个消息传递的实例:

  1. 当用户点击 #hideBtn 按钮时,发送一个消息指示 main 隐藏窗口。
  2. 默认情况下,窗口是半透明的;当用户点击窗口(实质上是激活了 body 上的 clickz 事件)时,发送一个消息指示 main 将窗口设置为完全不透明。

3
依我之见,这不是一个好的做法。现在你正在将本应由渲染器(处理 onClick)承担的责任放在了 preload.js 中。应该使用 contextBridge 来向渲染器公开 API。渲染器处理 onClick,调用通过 contextBridge 提供的 API,而 preload/main 则执行不安全的代码。 - passerby
1
这是架构个人偏好的问题。例如,我不喜欢依赖全局变量,比如 window.electron。此外,我会失去从 require 对象中获取的所有基于类型的好处。 - avalanche1
我正在使用TypeScript,并向由contextBridge提供的API添加了类型定义。 - passerby
哦,太好了!你能发个链接给我吗?让我看看如何将TS添加到Electron中? - avalanche1
1
我从这里得到了typings的想法:https://github.com/frederiksen/angular-electron-boilerplate。请查看src/window-interface/index.d.ts。 - passerby
显示剩余2条评论

0

你不需要使用nodeIntegration,因为它会带来安全问题,而是使用preload.js文件。

为什么?

-> Electron的主进程(main.js)是一个具有完全操作系统访问权限的Node.js环境,渲染进程(renderer.js)用于渲染网页,而不是Node.js,因为任何人都可以获取数据并查看功能。

-> 为了将Electron的不同进程类型连接在一起,我们需要一个特殊的脚本称为'preload.js'。

-> preload脚本在渲染器中的网页加载之前被注入。


2
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接