进阶 electronjs.org 2026-10-07 23:51:43 · 8 阅读
第26章 Electron Fuses:打包阶段的安全特性开关
Electron Fuses
打包时的功能开关
什么是 Fuses?
从安全角度来看,禁用某些未使用但功能强大、可能削弱应用安全性的 Electron 特性是合理的。例如,不使用 ELECTRON_RUN_AS_NODE 环境变量的任何应用都应禁用该特性,以防止一小类“就地取材(Living off the Land)”攻击。
我们不希望 Electron 的使用者为了达到此目的而进行 Fork,因为从源码构建并维护 Fork 分支是一项巨大的技术挑战,耗费大量时间和金钱。
Fuses 是解决这一问题的方案。从高层次来看,它们是 Electron 二进制文件中的“魔术位”,可以在打包 Electron 应用时翻转,以启用或禁用某些特性/限制。
由于这些位在代码签名应用之前、于打包阶段被翻转,操作系统负责通过 OS 级别的代码签名验证确保这些位未被逆转(例如 macOS 上的 Gatekeeper 或 Windows 上的 AppLocker)。
现有 Fuses
runAsNode
默认:启用
@electron/fuses: FuseV1Options.RunAsNode
runAsNode fuse 控制是否尊重 ELECTRON_RUN_AS_NODE 环境变量。禁用此 fuse 后,child_process.fork 会抛出异常,因为它依赖此环境变量才能工作。建议改用 Utility Processes,它适用于许多需要独立 Node.js 进程(例如 SQLite 服务器进程)的场景。
cookieEncryption
默认:禁用
@electron/fuses: FuseV1Options.EnableCookieEncryption
cookieEncryption fuse 控制磁盘上的 cookie 存储是否使用操作系统级别的加密密钥进行加密。默认情况下,Chromium 用于存储 cookie 的 SQLite 数据库以明文存储值。如果希望确保应用的 cookie 像 Chromium 一样加密,应启用此 fuse。请注意这是一次性迁移——启用此 fuse 后,现有未加密的 cookie 将在写入时加密,但随后禁用该 fuse 会导致 cookie 存储损坏且无法使用。大多数应用可以安全地启用此 fuse。
信息在 macOS 上,此 fuse 依赖于与 safeStorage 相同的操作系统级别 Keychain 访问权限,因此应用必须经过代码签名才能正常工作。
nodeOptions
默认:启用
@electron/fuses: FuseV1Options.EnableNodeOptionsEnvironmentVariable
nodeOptions fuse 控制是否尊重 NODE_OPTIONS 和 NODE_EXTRA_CA_CERTS 环境变量。NODE_OPTIONS 环境变量可用于向 Node.js 运行时传递各种自定义选项,通常在生产环境中的应用中不使用。大多数应用可以安全地禁用此 fuse。
nodeCliInspect
默认:启用
@electron/fuses: FuseV1Options.EnableNodeCliInspectArguments
nodeCliInspect fuse 控制是否尊重 --inspect、--inspect-brk 等标志。禁用时,它还确保 SIGUSR1 信号不会初始化主进程的 inspector。大多数应用可以安全地禁用此 fuse。
embeddedAsarIntegrityValidation
默认:禁用
@electron/fuses: FuseV1Options.EnableEmbeddedAsarIntegrityValidation
embeddedAsarIntegrityValidation fuse 切换 macOS 和 Windows 上的一个功能,该功能在加载 app.asar 文件时验证其内容。此功能设计为对性能影响最小,但可能会轻微减慢来自 app.asar 归档内部的文件读取速度。大多数应用可以安全地启用此 fuse。
有关如何使用 ASAR 完整性验证的更多信息,请阅读 Asar Integrity 文档。
onlyLoadAppFromAsar
默认:禁用
@electron/fuses: FuseV1Options.OnlyLoadAppFromAsar
onlyLoadAppFromAsar fuse 更改 Electron 用于定位应用代码的搜索系统。默认情况下,Electron 会按以下顺序搜索此代码:
app.asar
app
default_app.asar
启用此 fuse 后,Electron 将仅搜索 app.asar。与 embeddedAsarIntegrityValidation fuse 结合使用时,此 fuse 确保无法加载未经过验证的代码。
loadBrowserProcessSpecificV8Snapshot
默认:禁用
@electron/fuses: FuseV1Options.LoadBrowserProcessSpecificV8Snapshot
V8 快照可用于提高应用启动性能。V8 允许你对初始化的堆进行快照,然后重新加载它们,以避免初始化堆的开销。
loadBrowserProcessSpecificV8Snapshot fuse 更改浏览器进程使用的 V8 快照文件。默认情况下,Electron 的所有进程都使用相同的 V8 快照文件。启用此 fuse 后,主进程使用名为 browser_v8_context_snapshot.bin 的文件作为其 V8 快照。其他进程将使用它们通常使用的 V8 快照文件。
为渲染进程和主进程使用单独的快照可以提高安全性,特别是确保渲染器不使用启用了 nodeIntegration 的快照。
详情请参阅 electron/electron#35170。
当主进程运行在自定义 V8 快照上时(即此 fuse 或替换为 electron-mksnapshot 生成的 v8_context_snapshot.bin)—— Electron 将从源代码而非嵌入的 Node.js 启动快照中引导主进程的 Node.js 环境,从而使自定义快照中的对象对主进程可用。这会消耗部分主进程启动时间,而这些时间本可由嵌入式快照节省。
grantFileProtocolExtraPrivileges
默认:启用
@electron/fuses: FuseV1Options.GrantFileProtocolExtraPrivileges
grantFileProtocolExtraPrivileges fuse 更改通过 file:// 协议加载的页面是否获得传统 Web 浏览器之外的额外权限。此行为是早期版本 Electron 应用的核心,但现在已不再需要,因为应用应改为通过自定义协议提供本地文件。
如果不从 file:// 提供页面,应禁用此 fuse。
此 fuse 授予 file:// 协议的额外权限未在下方完整记录:
file:// 协议页面可以使用 fetch 通过 file:// 加载其他资源
file:// 协议页面可以使用 service workers
file:// 协议页面授予子框架在 file:// 协议上运行时通用访问权限,无论沙箱设置如何
wasmTrapHandlers
默认:启用
@electron/fuses: FuseV1Options.WasmTrapHandlers
wasmTrapHandlers fuse 控制 V8 是否使用信号处理器来捕获 WebAssembly 的越界内存访问。该功能通过在 WebAssembly 内存周围设置大型保护区域,然后安装一个信号处理器来捕获访问保护区域内存的尝试来实现。该功能仅支持以下 64 位系统:
Linux, macOS, Windows - x86_64
Linux, macOS - aarch64
| Guard Pages | WASM 堆 | Guard Pages |
|-----8GB-----| |-----8GB-----|
当该 fuse 被禁用时,V8 会在生成的 WebAssembly 代码中使用显式的边界检查来保证内存安全。不过这种方式有一些缺点: 编译器需要为每次内存引用生成额外的节点,处理这些节点会导致编译时间变长。 这些额外的节点又会产生大量额外的代码,使 WebAssembly 模块变得比理想情况下更大。 这些额外的代码——尤其是每次内存引用前的比较和分支操作——会带来明显的运行时开销。 如何翻转 fuses? 简单方式 @electron/fuses 是一个专门用来简化 fuse 翻转操作的 JavaScript 工具。具体用法和可能的报错情况,请查看该模块的 README。 const { flipFuses, FuseVersion, FuseV1Options } = require('@electron/fuses')
flipFuses(
// Path to electron
require('electron'),
// Fuses to flip
{
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false
}
)
你可以用 @electron/fuses 的 CLI 来验证已翻转的 fuses,或查看任意 Electron 应用的 fuse 状态。 npx @electron/fuses read --app /Applications/Foo.app
注意:如果你使用 Electron Forge 分发应用,可以通过 @electron-forge/plugin-fuses 来翻转 fuses,该插件已在所有模板中预装。 复杂方式 info术语表: Fuse Wire:Electron 二进制文件中用于控制 fuses 的一段字节序列 Sentinel:一段固定且已知的字节序列,可用于定位 fuse wire Fuse Schema:fuse wire 的格式及允许的取值 手动翻转 fuses 需要直接编辑 Electron 二进制文件,把其中的 fuse wire 修改为代表目标 fuse 状态的字节序列。 在 Electron 二进制文件的某个位置,会存在一段类似这样的字节序列: | ...binary | sentinel_bytes | fuse_version | fuse_wire_length | fuse_wire | ...binary |
sentinel_bytes 始终为该固定字符串:dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX fuse_version 为单字节,其无符号整数值代表 fuse schema 的版本 fuse_wire_length 为单字节,其无符号整数值表示后续 fuse wire 中 fuse 的数量 fuse_wire 是长度为 N 的字节序列,每个字节代表一个 fuse 及其状态。 "0"(0x30)表示 fuse 处于禁用状态 "1"(0x31)表示 fuse 处于启用状态 "r"(0x72)表示该 fuse 已被移除,此时将字节改为 1 或 0 均无效。 要翻转某个 fuse,只需在 fuse wire 中定位其位置,并根据目标状态将其改为 "0" 或 "1"。 你可以在此处查看当前 schema。
|-----8GB-----| |-----8GB-----|
当该 fuse 被禁用时,V8 会在生成的 WebAssembly 代码中使用显式的边界检查来保证内存安全。不过这种方式有一些缺点: 编译器需要为每次内存引用生成额外的节点,处理这些节点会导致编译时间变长。 这些额外的节点又会产生大量额外的代码,使 WebAssembly 模块变得比理想情况下更大。 这些额外的代码——尤其是每次内存引用前的比较和分支操作——会带来明显的运行时开销。 如何翻转 fuses? 简单方式 @electron/fuses 是一个专门用来简化 fuse 翻转操作的 JavaScript 工具。具体用法和可能的报错情况,请查看该模块的 README。 const { flipFuses, FuseVersion, FuseV1Options } = require('@electron/fuses')
flipFuses(
// Path to electron
require('electron'),
// Fuses to flip
{
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false
}
)
你可以用 @electron/fuses 的 CLI 来验证已翻转的 fuses,或查看任意 Electron 应用的 fuse 状态。 npx @electron/fuses read --app /Applications/Foo.app
注意:如果你使用 Electron Forge 分发应用,可以通过 @electron-forge/plugin-fuses 来翻转 fuses,该插件已在所有模板中预装。 复杂方式 info术语表: Fuse Wire:Electron 二进制文件中用于控制 fuses 的一段字节序列 Sentinel:一段固定且已知的字节序列,可用于定位 fuse wire Fuse Schema:fuse wire 的格式及允许的取值 手动翻转 fuses 需要直接编辑 Electron 二进制文件,把其中的 fuse wire 修改为代表目标 fuse 状态的字节序列。 在 Electron 二进制文件的某个位置,会存在一段类似这样的字节序列: | ...binary | sentinel_bytes | fuse_version | fuse_wire_length | fuse_wire | ...binary |
sentinel_bytes 始终为该固定字符串:dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX fuse_version 为单字节,其无符号整数值代表 fuse schema 的版本 fuse_wire_length 为单字节,其无符号整数值表示后续 fuse wire 中 fuse 的数量 fuse_wire 是长度为 N 的字节序列,每个字节代表一个 fuse 及其状态。 "0"(0x30)表示 fuse 处于禁用状态 "1"(0x31)表示 fuse 处于启用状态 "r"(0x72)表示该 fuse 已被移除,此时将字节改为 1 或 0 均无效。 要翻转某个 fuse,只需在 fuse wire 中定位其位置,并根据目标状态将其改为 "0" 或 "1"。 你可以在此处查看当前 schema。