- 人工智能
- AI 应用
- 桌面应用
- AI Agent
- MCP 服务
- AI 技能
【免费下载链接】genoffice
Free, open-source AI Office suite: Docs, Sheets, Slides, PDF, Markdown and HTML editors with a built-in AI agent, plus a `genoffice` CLI and agent skill so Claude Code, Codex and Cursor can create and edit real .docx/.xlsx/.pptx files locally. Bring your own key. macOS, Windows & Linux.
GenOffice 是一款开源的 AI 办公套件,内置 Docs、Sheets、Slides、PDF、Markdown 与 HTML 编辑器以及内嵌 AI Agent。由于 AI 生成的内容(布局脚本、HTML、外部链接、IPC 消息)会直接进入渲染进程与主进程的处理链路,官方在 SECURITY.md 中建立了一套“纵深防御 + 能力最小化”的安全模型。本文以该文档为骨架,结合仓库源码逐层拆解其漏洞报告流程、进程安全基线、外部链接统一出口,以及两个核心威胁模型——AI 生成布局脚本的解释器沙箱与 AI 生成 HTML 的隔离导出窗口,帮助读者理解这套体系如何在不牺牲 AI 能力的前提下封堵渲染进程攻击面。
漏洞报告流程:私密上报,72 小时响应
SECURITY.md 对安全问题处理方式有明确约定:
- 所有疑似漏洞必须通过 GitHub 的private vulnerability reporting(私有漏洞报告)功能私密提交,禁止在公开 issue 中讨论安全问题;
- 项目方承诺在72 小时内确认收到的报告;
- 报告提交入口位于仓库的安全咨询页面,任何安全研究人员、用户或下游集成方都应遵循这一流程,避免漏洞在修复前被公开扩散。
这一“私有上报、限期确认”的流程是整套安全工程的外部收口:内部再多的加固手段,也需要一个受控的反馈通道来持续发现边界绕过(例如本文后面提到的“布局脚本越权触达”类问题)。
进程安全基线:每个窗口都启用完整渲染器锁定
GenOffice 的架构决定了其渲染进程是安全边界的第一道防线:每个应用(docs、sheets、slides、pdf、markdown、shell、updater)都会打开独立的文档窗口或标签页,其中加载的既有人工编辑的内容,也有 AI 生成的内容。因此 SECURITY.md 规定,所有窗口统一启用完整的 Electron 渲染器锁定:
contextIsolation: true—— 渲染进程与 preload 暴露的 API 之间隔离上下文;nodeIntegration: false—— 渲染进程无法直接访问 Node.js 能力;sandbox: true—— 渲染进程运行在 OS 级沙箱中。
以 docs-main.ts 为例,其 BrowserWindow 配置明确写入了上述三项;html-main.ts、markdown-main.ts、pdf-main.ts、sheets-main.ts 等应用的主进程代码中同样保持一致。可以推断,这一配置是各应用共享的工程基线,而非个别窗口的特殊处理。
在渲染器锁定之外,主进程还承担了两类集中管控:
1. 类型化、校验过的 IPC 通道。渲染进程只能通过类型化、经过校验的 IPC 通道触达主进程,且 payload 会在主进程侧做 schema 校验;其中sheets 应用在端到端链路上使用 zod进行校验。这意味着即使某个渲染进程被攻破,其能够调用的主进程能力也被限制在已注册、已验证的通道集合内,无法通过任意消息驱动任意主进程功能。
2. 全应用导航锁定。navigation-guard.ts 通过web-contents-created为每一个 webContents 注册两类默认拦截:will-navigate中,任何与当前 URL 不一致的全页导航都会被preventDefault(正常运行时渲染进程是只加载一次的本地 SPA,全页导航只可能来自恶意文档内容或 AI 生成的标记);同时为window.open安装默认拒绝(deny)的处理器。个别应用(docs/slides/pdf)会针对特定 webContents 用safeExternalUrl替换该默认处理器,仅放行白名单协议的外部链接。
外部链接统一出口:safeExternalUrl协议白名单
SECURITY.md 强调:每一次shell.openExternal调用都必须经过单一共享门禁,即@genoffice/electron-utils中的safeExternalUrl。其实现位于 safe-external-url.ts:
- 输入必须是字符串,否则直接返回
null; - 使用 WHATWG
URL解析器解析(失败即拒绝); - 协议必须命中白名单(默认仅
http:/https:),PDF 的链接批注场景可额外通过allowedProtocols选项放行mailto:; - 返回的是trim 后的原文,而不是解析后的对象——因为 WHATWG 解析器会静默剥掉首尾空白,若校验时用解析结果、打开时用原始输入,
shell.openExternal可能拿到一个校验时能通过但实际无法打开的空壳 URL。
因此file:、javascript:、smb:等自定义协议以及httpx:这类前缀伪装 URL 一律被拒绝。配套测试 safe-external-url.test.ts 覆盖了非字符串输入、畸形 URL、危险协议、自定义白名单与空白 trim 等场景,例如file:///etc/passwd、javascript:alert(1)、smb://server/share均被断言为null。
从调用面看,docs-main.ts、slides-main.ts、pdf-main.ts、markdown-main.ts 等主进程文件以及渲染侧(如 export-links.ts、SlideShowView.tsx)都引用了这一工具,印证了“单一共享门禁”的说法——任何外部跳转都必须先过这道协议白名单。
凭据安全:不硬编码 API Key,走登录账户代理
SECURITY.md 还明确了 AI 场景下的凭据策略:
- 任何 API Key 都不允许硬编码进仓库;
- AI 请求默认通过已登录账户代理发出;
- 用户自带 Key(Bring Your Own Key 模式)时,Key 只存放于OS 级别的设置存储(system-level settings store),不会落入文档或普通配置文件。
这一设计将“密钥泄露”从代码仓库层面整体移除,把风险收敛到系统凭据存储这一标准 OS 边界上,同时保留了用户自带 Key 的灵活性。
威胁模型一:AI 生成的幻灯片布局脚本
为什么需要解释器而不是eval
Slides 的 AI 通过生成一段“小脚本”来调整版式。关键安全决策是:这段源码看起来像 JavaScript,但绝不会以可执行源码的形式交给eval、Function、VM 上下文、worker 或 JavaScript 引擎。它由Acorn 解析成 AST,再由一个受限的 AST 解释器执行,实现位于 layout-script-interpreter.ts。其文件头注释明确写道:“Parse and execute the layout DSL without invoking JavaScript”(解析并执行布局 DSL,但不调用 JavaScript)——这是整个威胁模型的根基:把“模型输出”和“可执行代码”之间的一切动态求值通路全部切断。
脚本按设计可以做什么
SECURITY.md 给出了明确的能力清单:
- 读取
els/canvas的无原型 JSON 副本(不是宿主对象,无法借此访问原型链); - 执行有上限的算术与流程控制(
Math辅助函数、数组/字符串/正则方法白名单); - 调用 8 个编辑原语:
setBox、moveBy、resizeBy、setText、setStyle、setFill、setStroke、log。
每个编辑原语都会校验参数(元素是否存在、只读标志、数值是否有限、颜色是否为十六进制格式),并且只把操作写入一个op 缓冲区;该缓冲区最终通过与手工编辑完全相同的命令管线应用。也就是说,AI 脚本对版式的任何改动都走正规编辑流程,不存在绕过校验的“快车道”。
以 layout-script.ts 为例:setFill要求颜色必须是#RRGGBB或'none',否则抛错;setStroke要求color为#RRGGBB、widthPt必须为正的有限数值;log有 50 条的上限(logs.length >= 50时静默丢弃)。而 layout-script.ts 中,解释器一旦抛错,整个调用的返回结果是{ ops: [], edits: [], logs, error: msg }——所有已缓冲的操作被整体丢弃,绝不允许“执行到一半的脏状态”进入文档。
解释器边界:五条硬约束
SECURITY.md 定义了五层边界,逐一对应到源码实现:
1. 标识符只在解释器自有的词法作用域内解析。layout-script-interpreter.ts 的Scope类维护valuesMap 与父作用域链,get未命中时直接抛Unknown identifier ... Only the documented layout-script API is available。根作用域(root)只注入els、canvas的克隆副本、8 个编辑原语、Math/Number/String/Boolean/Object/Array/JSON的白名单辅助。没有环境全局变量、没有模块加载器、没有 DOM、没有网络、没有 IPC 桥、没有定时器、没有 process API、没有任何动态代码原语。
2. 属性读取按值类型分派。getMember(layout-script-interpreter.ts)只对以下类型放行:数组(length、数字下标、约 16 个方法的显式白名单)、字符串(length、下标及includes/startsWith/split等白名单方法)、有界正则对象(仅test)、Builtin的显式members、以及无原型的普通数据对象(只读自有字段)。其余一切访问(包括计算属性名解析到不允许的方法)都会抛错。关键点在于:宿主原型和函数属性永远不会被遍历——即使脚本用constructor、__proto__或任意计算属性名去探测,也拿不到宿主对象。
3. 调用只接受解释器创建的函数或显式内建函数。invoke(layout-script-interpreter.ts)只认两类值:Builtin或ScriptFunction,否则抛Only documented functions and safe collection methods can be called。宿主函数无法通过构造器/原型链被“具象化”(represented),自然也就无法被脚本调用。cloneData(layout-script-interpreter.ts)会把进入编辑原语的输入与返回值递归复制成无原型、JSON 化的数据,且拒绝非有限数字与超深嵌套。
4. 错误即回滚,日志有上限。如前所述,任何异常都丢弃全部缓冲操作,log日志上限 50 条,防止脚本用日志轰炸界面。
5. 执行有步数与调用深度上限。常量定义在 layout-script-interpreter.ts:
| 常量 | 值 | 作用 |
|---|---|---|
MAX_STEPS | 25 000 | 语句/表达式总步数上限,拦截死循环与失控递归 |
MAX_CALL_DEPTH | 64 | 调用深度上限,配合步数限制兜底 |
MAX_COLLECTION_SIZE | 10 000 | 数组/集合大小上限,防止内存膨胀 |
其中tick(layout-script-interpreter.ts)在每步执行前累加并检查MAX_STEPS,超限时报出带行号的错误。
正则有独立预算。布局脚本支持正则字面量,但绝不让它落到原生引擎——因为原生匹配发生在解释器步数记账之外,灾难性回溯(catastrophic backtracking)的正则会绕过步数限制卡死渲染进程。因此仓库实现了自有的有界正则引擎bounded-regex.ts,把正则先编译成自己的 AST(字面量、字符类、分组、分支、量词、锚点、i/m/s标志),再由带预算的匹配器执行:
| 常量 | 值 |
|---|---|
MAX_PATTERN_LENGTH | 500 字符 |
MAX_REPEAT_COUNT | 1000 次重复 |
MAX_MATCH_STEPS | 1 000 000 步 |
MAX_MATCH_DEPTH | 2000 层 |
它显式拒绝反向引用、命名反向引用、Unicode 属性转义、lookahead/lookbehind 等可能引入复杂回溯的语法(unavailable(...)直接抛错),每次匹配步进都扣减budget,超限抛Layout script regular expression exceeded its execution budget。这就保证了即使恶意构造最坏情形的模式,也无法在预算之外卡住渲染进程。
边界之外:渲染器沙箱只是兜底
SECURITY.md 特别强调了一个容易被误解的点:Electron 渲染器沙箱在这里只是纵深防御(defense in depth),并不是布局脚本的安全边界。设计目标是把问题消灭在更前面——布局脚本在解释器模型里从一开始就无法获得渲染进程的能力(网络、存储、IPC、主进程访问)。文档呼吁:如果你能找到一条路径让布局脚本触达注入原语之外的东西(网络、存储、按设计不可达的 IPC 通道或主进程),那就是一个值得上报的漏洞。这正是本文开头所述漏洞报告流程与威胁模型之间的闭环关系。
威胁模型二:渲染 AI 生成的 HTML(幻灯片导出)
Slides 的HTML-to-pptx 导出管线需要在隐藏的BrowserWindow中渲染 AI 生成的 HTML。SECURITY.md 对此窗口采取“视同敌对内容”的处置:
- 完整渲染器锁定:
sandbox: true、contextIsolation: true、nodeIntegration: false; - 无 preload 脚本、无 IPC 面——该窗口不暴露任何 IPC 通道,主进程是它的唯一驱动方;
- 主进程仅通过
executeJavaScript驱动其执行; - 窗口在watchdog 超时机制下被销毁,防止渲染卡死影响主流程。
与之相呼应的代码证据遍布各应用:例如 pdf-main.ts、docs-main.ts、html-main.ts、markdown-main.ts、sheets-main.ts 中都存在new BrowserWindow({ show: false, webPreferences: { sandbox: true, javascript: false } })的隐藏窗口模式,可见“无脚本、无沙箱逃逸面”的隐藏窗口是各应用处理不可信内容(导出、打印、渲染)时的统一做法。也就是说,即使 AI 生成的 HTML 里夹带了恶意脚本或试图导航到危险 URL,它也被限定在一个没有 preload、没有 IPC、有 watchdog 的隔离窗口中,无法触达应用数据与主进程。
范围外(Out of Scope)声明
SECURITY.md 明确了以下两类问题不在本仓库安全承诺之内:
- 云端 AI 服务:本客户端所对接的云 AI 服务独立运营,不在本仓库范围内,相关问题应走服务提供方的渠道上报;
- 需要“机器已被攻破”前提的漏洞:包括本地开发特意保留的环境变量覆盖点
GSK_CLI_PATH与XLSX_SIDECAR_PATH。设置这些变量需要控制进程环境,而这本身就等价于在该机器上执行代码——因此不将其视为本应用引入的漏洞。
理解这条边界很重要:它把安全承诺严格限定在“应用代码自身”这一层,避免把供应链、宿主系统与云服务的问题混入本仓库的漏洞管理流程。
总结:三层防御,能力最小化优先
纵观 SECURITY.md 与仓库实现,GenOffice 的安全模型可以归纳为三个层次:
- 进程层:所有窗口统一
contextIsolation/nodeIntegration: false/sandbox,配合导航锁定与默认拒绝的window.open; - 数据层:IPC 全链路类型化校验(sheets 端到端 zod)、外部链接过
safeExternalUrl协议白名单、API Key 不进仓库只进 OS 凭据存储; - AI 内容层:AI 布局脚本走 Acorn AST + 受限解释器(无
eval/Function/VM)、有步数与正则预算、错误即回滚;AI 生成的 HTML 只在无 preload、无 IPC、受 watchdog 保护的隐藏窗口渲染。
整套体系的核心哲学是能力最小化:不是“在沙箱里运行不可信代码再祈祷它不逃逸”,而是让不可信代码在模型层就无法表达宿主能力。对于任何想要在自己的 Electron + AI 产品中安全地引入“模型生成代码/标记”能力的开发者,SECURITY.md、layout-script-interpreter.ts、bounded-regex.ts、safe-external-url.ts 与 navigation-guard.ts 构成了一份可直接对照学习的参考实现。
- 人工智能
- AI 应用
- 桌面应用
- AI Agent
- MCP 服务
- AI 技能
【免费下载链接】genoffice
Free, open-source AI Office suite: Docs, Sheets, Slides, PDF, Markdown and HTML editors with a built-in AI agent, plus a `genoffice` CLI and agent skill so Claude Code, Codex and Cursor can create and edit real .docx/.xlsx/.pptx files locally. Bring your own key. macOS, Windows & Linux.
相关推荐
drawio-desktop沙箱模式:渲染进程安全隔离技术
drawio desktop沙箱模式:渲染进程安全隔离技术 引言:Electron应用的安全挑战 在现代桌面应用开发中,Electron框架凭借其跨平台能力和W
桌面应用图形学终极指南:如何利用Tampermonkey安全沙箱保护你的浏览器环境
终极指南:如何利用Tampermonkey安全沙箱保护你的浏览器环境 Tampermonkey作为最受欢迎的用户脚本管理器,拥有超过1000万用户,支持Chro
前端插件系统OpenManus DockerSandbox 深度解析:用 Docker 容器为 AI Agent 代码执行构建安全隔离沙箱
OpenManus DockerSandbox 深度解析:用 Docker 容器为 AI Agent 代码执行构建安全隔离沙箱 本文围绕 OpenManus 教
人工智能AI 应用AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考