Electron contextBridge
contextBridge 在 contextIsolation 开启时把 preload 的 Isolated World 与页面 Main World 连接起来,并以可序列化、可审核的窄接口暴露能力;它不是通用对象共享机制,也不应直接暴露 ipcRenderer。
[!info] related notes
- 所属 MOC: Electron MOC
- 前置: Electron preload 脚本, Electron contextIsolation
- IPC: Electron IPC 模式
- 安全: Electron 安全检查清单
Electron contextBridge
一句话定义
contextBridge 是 Electron 在 contextIsolation 模型下提供的跨 JavaScript context 桥接机制。
典型关系:
preload / Isolated World
→ contextBridge
→ renderer / Main World
它的目标不是“共享整个 preload 环境”,而是:
只把明确允许的、最小的 API contract 暴露给页面。
为什么需要它
contextIsolation 开启后:
- preload 与页面不是同一个 JavaScript context;
- preload 的 window 与页面的 window 不相同;
- 页面不能直接读取 preload 内部对象。
因此旧写法:
window.myApi = …
不能再作为隔离模式下的标准桥接方案。
应通过:
contextBridge.exposeInMainWorld(…)
显式定义页面可见能力。
API Contract
推荐暴露:
- getVersion()
- openFile()
- saveSettings(input)
- onUpdateAvailable(callback)
这类业务语义 API。
不推荐暴露:
- ipcRenderer
- fs
- shell
- process
- 任意 invoke(channel, payload)
- 任意 send(channel, payload)
原因是:
renderer compromise 时,攻击者只能使用你暴露的 capability。
API 越窄,攻击面越小。
Electron 29 的边界
Electron 29 起,ipcRenderer 不能整体通过 contextBridge 传递。
这不仅是 API breaking change,也是明确的安全方向:
不要把整个 IPC surface 暴露给 renderer。
官方推荐提供 safe wrapper,例如只暴露某个固定事件或固定 invoke。
跨 Bridge 的值
contextBridge 并不是任意 JavaScript object 的共享内存。
跨 context 的参数、返回值会受到类型与序列化约束。
常见简单数据:
- string
- number
- boolean
- Array
- plain object
通常更适合作为 contract。
自定义 prototype、某些复杂对象、Symbol 等不能按普通引用语义跨 bridge 使用。
因此 renderer-facing API 应倾向:
plain serializable data + functions
而不是暴露底层 Electron object。
Callback Wrapper
如果 main 向 renderer 推送事件,不要把原始 IPC event 对象直接传给页面。
错误方向:
renderer callback(event, data)
其中 event 可能携带 sender 等高权限相关能力。
更安全:
preload 接收 IPC event
→ 丢弃 event object
→ 只把已验证 data 传给 renderer callback
这与 Electron 官方 IPC 教程的安全建议一致。
TypeScript
对于 renderer:
window.appApi.openFile()
应该有明确类型。
推荐把 renderer-facing interface 单独定义:
- API method name;
- input;
- output;
- event payload。
然后通过 global Window declaration 暴露类型。
这样 preload contract 既是安全边界,也是编译期 contract。
contextBridge 不是 Validation Layer 的全部
即使 renderer 只能调用:
window.appApi.deleteFile(path)
main 进程仍然必须验证:
- sender;
- path;
- permission;
- domain rule;
- current app state。
因为 renderer 输入仍然应该视为不可信。
常见失败模式
万能 send/invoke
页面自己选择 channel。
→ 白名单边界基本失效。
把 Node object 暴露出去
→ capability 太大,且跨 bridge 语义不稳定。
callback 泄漏 IPC event
→ renderer 获得超出预期的底层对象。
API 名称只反映技术实现
例如:
invokeFileChannel()
比:
openFile()
更容易让 renderer 和 IPC implementation 强耦合。
学习检查
- contextBridge 为什么在 contextIsolation 开启时需要存在?
- Isolated World 与 Main World 的 window 为什么不是同一个对象?
- 为什么 openFile() 比 invoke(channel) 更安全?
- Electron 29 对 ipcRenderer 跨 bridge 做了什么限制?
- 为什么 callback 不应直接收到原始 IPC event?
- contextBridge 为什么不等于任意对象共享机制?
- 设计一个 settings API,只暴露 getSettings / updateSettings 两个 renderer capability。