状态:权威开发规则(authoritative) 读取时机:修改 Desktop Renderer、preload、BrowserWindow、WebView、IPC、CSP、 导航、外链、文件/数据库/凭证访问或 Electron 特权能力之前
本规则以 Electron 官方安全指南 为基线,并结合 Cindy 当前 Electron 41 的实际架构补充项目约束。安全优先级高于为了 调用方便而缩短代码路径。
增量适用原则:本规则约束新代码和新能力,不要求为了统一形式而专项改造存量代码。 存量安全债务只有在用户明确要求治理时才单独处理;普通功能修改不要顺带扩大重构范围。
- Renderer 是不可信 UI 环境。Agent 输出、Markdown、文件预览、网页内容、插件内容和 用户输入都可能携带恶意数据;Renderer 中发生 XSS 时,不得因此获得 Node、文件系统、 数据库、凭证或任意 IPC 能力。
- preload 是最小权限桥,不是 Renderer 的通用后门。
- Main 是特权与信任边界:系统 API、持久化、网络凭证、文件访问、进程管理和权限判断 必须在 Main 或受控的独立进程中完成。
- IPC payload、URL、路径和 Renderer 自报的身份都不可信。Main 必须重新验证来源、类型、 长度、范围、归属和权限。
这里按“能力是否有特权、输入是否可信”划分边界,不机械地按“是不是业务逻辑”划分。 无特权的纯计算和展示编排可以留在 Renderer 或下沉到 package;只有需要系统权限、持久化、 凭证、受控网络或安全裁决的行为必须跨过受审计的 IPC 进入 Main。
Renderer 可以负责组件渲染、交互状态、表单状态、展示数据转换和纯 UI 校验,但必须遵守:
- 不在运行时 import
electron、node:*,不使用require获取 Node 或 Electron 能力。 仅用于 DOM/WebView 类型声明且编译后完全擦除的import type可以保留。 - 不直接读写磁盘、数据库、系统凭证、环境变量或启动子进程。
- 不保存长期业务真相;持久状态由 Main 或领域 package 管理,Renderer 只持有视图状态和 可重建缓存。
- 不把前端隐藏、按钮禁用或 prompt 当成权限边界。
- 不为了绕过 IPC 在 Renderer 中新增带凭证的网络请求。无凭证的公开资源请求必须受 CSP 约束,并确认不会泄露本地数据。
- Main 负责 Electron 生命周期、窗口、安全策略、IPC 授权、OS 集成、持久化和特权副作用。
- 可复用领域逻辑放在 packages,通过接口注入文件、网络或宿主能力;package 不直接依赖 Renderer 组件,也不反向 import Desktop Main。
- shared 只存跨进程协议、类型、常量和纯函数,不放持久化、网络或系统副作用。
新增 BrowserWindow 时必须显式配置下列安全选项,不得只依赖 Electron 默认值:
sandbox: truecontextIsolation: truenodeIntegration: falsenodeIntegrationInSubFrames: falsenodeIntegrationInWorker: falsewebSecurity: trueallowRunningInsecureContent: falseexperimentalFeatures: false- 不设置
enableBlinkFeatures plugins: falsenavigateOnDragDrop: false
窗口可以追加 preload、partition、节流等功能配置,但不得覆盖上述字段为更宽松的值。
webviewTag 默认关闭;受控例外目前有三处——主界面与右侧栏子窗口(内置浏览器需要),
以及插件面板独立窗口(main/ghost-panel-window/window.ts,仅承载 ghost 分区面板
webview)——所有例外都必须继续由 webview-security.ts 在 will-attach-webview 阶段
覆盖 Renderer 传入的全部安全选项(hardener 挂在 web-contents-created,对所有窗口
全局生效,ghost 附加闸只认分区与地址、不认宿主窗口)。
内置浏览器为了捕获 window.open,会在 Main hardener 中设置 allowpopups,随后由
setWindowOpenHandler 拒绝真实弹窗并转成受控标签页。这是唯一允许的窄例外;Renderer
不得自行添加 allowpopups。Ghost 面板更严格:专属 partition、无 Node、无通用 preload,
身份由 Main 根据真实 webContents 反查。
- 只通过
contextBridge.exposeInMainWorld暴露按用途命名的最小方法。 - 禁止暴露原始
ipcRenderer、ipcRenderer.on、send、invoke或允许 Renderer 自选 channel 的通用函数。 - 订阅事件时必须在 preload 内丢弃
IpcRendererEvent,只把经过约束的业务 payload 传给 Renderer;不得把 Electron event 对象传给回调。 - 每个 bridge 方法只完成一个明确动作,并使用明确的参数和返回类型。新增能力时同步更新 shared 类型、preload 声明、Main handler、错误协议和测试。
- preload 不读取或返回凭证明文,不向 Renderer 暴露 Node 对象、文件句柄、WebContents、 Session 等特权对象。
- 新增
ipcMain.handle/on,或给旧 handler 扩展新的特权能力时,默认验证event.senderFrame来自 Cindy 自有顶层 frame;Ghost、WebView、全局浮层等特殊来源 必须使用各自的身份注册表和能力白名单。 - Main 不信任 Renderer 传入的 userId、ghostId、窗口 ID、文件归属或权限结论;身份从
event.sender/senderFrame与 Main 持有的注册关系反查。 - handler 在执行副作用前验证 payload 的结构、长度、枚举值、路径范围和资源归属。仅有 TypeScript 类型不等于运行时校验。
- 文件、目录、媒体和保存位置使用受控 grant/deposit/ledger 或已有安全服务,不把 “Renderer 传来一个绝对路径”视为授权。
- 错误使用统一 IPC 错误协议,不把堆栈、凭证、内部绝对路径或敏感响应原样返回 Renderer。
存量 IPC 数量较大,尚未全部迁入统一 sender guard。本规则不触发存量 handler 专项整改, 但新增 handler 不得以“旧代码没校验”为理由省略 sender 与 payload 验证。
- Cindy 自有 Renderer 不加载远程应用代码。远程网页进入隔离 WebView,普通链接交给系统 浏览器。
- 所有导航和新窗口请求由 Main 的
will-navigate与setWindowOpenHandler限制;禁止 Renderer 自行放宽。 shell.openExternal只接受经过URL解析和协议白名单验证的目标。用户可点击的普通 外链限http:/https:;系统设置等自定义协议必须是静态精确白名单。- 禁止把未验证的命令、文件 URL 或任意自定义 scheme 交给系统 shell。
- WebView 的 partition、preload、popup、权限请求和下载行为由 Main 决定,不信任标签属性。
- 新增加载远程内容的 session 时,必须同时设置
setPermissionRequestHandler与setPermissionCheckHandler,按最小权限默认拒绝;需要放行时必须校验真实来源与权限类型。
- 应用 CSP 由
main/security/csp.ts统一注入。不得另注册会覆盖它的session.webRequest.onHeadersReceivedlistener。 - 正式包不得新增远程脚本或
'unsafe-inline'。现有'unsafe-eval'仅为 vendored drawio 的已知例外;新增用途必须先做安全评估,不能顺手扩大script-src、connect-src或frame-src。 - 新的本地资源通道优先使用范围受控的自定义协议,不新增
file://读取路径。主 Renderer 仍使用file://是存量架构,迁移需要单独设计和验证,不能在普通功能 PR 中顺带改动。 - 打包必须保留现有 Fuses:关闭 RunAsNode、Node options 和 CLI inspect,开启 cookie 加密、 ASAR 完整性校验并只从 ASAR 加载应用。
- 使用当前受支持的 Electron 版本;升级时重新核对官方安全清单、默认值变化和 breaking changes,不因“默认已经安全”删除显式配置。
新增或扩展相关能力时至少回答:
- Renderer 是否获得了新的特权数据或能力?能否进一步缩小?
- BrowserWindow/WebView 是否保持沙箱、上下文隔离、无 Node 和 Web Security?
- preload 是否只暴露固定方法并剥离 Electron event?
- IPC 是否验证 sender、payload、资源归属和权限?
- URL、导航、外链、下载和自定义协议是否 fail closed?
- CSP 或 Fuses 是否被放宽?若有,为什么不可避免,风险如何验证?
- 新增安全边界是否补了能阻止回退的自动测试?
最小验证入口:
pnpm --filter desktop exec vitest run src/main/__tests__/webview-security.test.ts src/main/security/__tests__/csp.test.ts
pnpm --filter desktop typecheck