来源标注 / Provenance
| 项 |
内容 |
| 来源渠道 |
私密邮件反馈 → feedback@cindy.app |
| 报告者 |
岳巍(外部报告者) |
| 报告日期 |
2026-07-25 (Asia/Shanghai) |
| 本 issue 由 |
维护者代为转录并补充代码定位 |
| 披露范围 |
⚠️ 本 issue 为删节版。按 SECURITY.md,复现步骤、可利用路径与相关调用链不在公开渠道披露,仅在私密渠道与报告者协调。此处只保留架构缺口描述与加固需求。 |
一、报告者原文(节选转录)
我想私密反馈一个与 Cindy Desktop 本地插件更新流程有关的潜在安全问题。
当 Forge/Agent 直接使用某个已安装插件目录制作新版本时,源码修改会在用户确认更新之前直接改变安装目录中的 ghost.json。后续更新流程可能无法取得最近一次经过用户批准的旧 manifest,从而导致更新确认弹窗中的版本变化显示错误。
初步代码检查还发现,权限差异和部分运行时授权判断也可能依赖安装目录中的当前 manifest,而不是宿主独立保存的已批准状态。因此,该问题可能不只是界面显示错误,还可能涉及插件权限确认和运行时授权边界。
关于流程代表性,报告者的说明(原文):
项目文档建议插件源码位于用户 workdir 中的独立目录。因此,直接在已安装插件目录中制作更新不是推荐的插件开发流程。
不过,目前产品流程中存在以下情况:
- 插件详情界面能够显示、复制或打开插件安装目录。
- 用户或 Agent 可以自然地进入该目录继续修改插件。
- Forge/Agent 当前没有阻止已安装插件目录被用作源码目录。
- 更新流程也没有检测当前安装状态是否已在确认前发生改变。
因此,我认为该问题不能完全归因于用户未遵守推荐流程。即使正常流程建议使用独立 workdir,宿主仍应保护安装目录、已批准 manifest 和运行时授权边界,避免自然可达的操作路径导致确认信息失真。
报告者的预期结果(原文):
- 更新弹窗中的旧版本必须来自最近一次经过用户确认的安装状态。
- 旧权限和旧能力声明必须来自宿主保存的 approved manifest,而不是安装目录中的当前
ghost.json。
- 候选插件的 manifest 应与当前已批准 manifest 独立保存和比较。
- 用户确认更新之前,对候选插件的修改不应改变当前插件的运行时授权状态。
- 如果检查完成后当前安装状态发生变化,原更新确认应失效,并要求用户重新检查和确认。
- Forge/Agent 不应允许把已安装插件目录或其子目录直接作为插件源码目录。
二、架构缺口(维护者确认,main @ 39afc9b)
报告者的判断成立。核心是一句现有设计注释(apps/desktop/src/main/cindy-brain/GhostManager.ts:86):
目录即注册表:没有额外的 DB / 索引文件,list() 每次实扫磁盘
即:宿主不保存任何独立的、经用户批准的 manifest 副本。插件安装目录中的 ghost.json 是唯一事实源,而它是可变的、不在宿主保护范围内的普通文件。
这一个根因派生出三处依赖:
- 更新弹窗的"旧版本" ——
apps/desktop/src/renderer/cindy-brain/installFlow.tsx:102,from 取自 installed.manifest.version,installed 来自现读安装目录。
- 更新弹窗的权限 diff 基准 ——
installFlow.tsx:98,diffGhostPermissionItems(installed.manifest, manifest) 的左值是同一份现读结果。
- 运行时能力判断 —— 读取
manifest.slots / manifest.network 等声明的路径同样经 GhostManager.list() 取得,未与已批准状态解耦。
此外 apps/desktop/src/main/cindy-brain/forge.ts:528 的 packGhostDir(dir) 只接受目录参数,无路径归属校验;而同文件 :394 的 scaffoldGhostDir 已有完整的 realpath + 会话 workdir 收敛校验。两者护栏不对称。
同样地,ghosts:update(apps/desktop/src/main/cindy-brain/index.ts:3144)已用 expectedPackageSha256 防止候选包在用户确认后被替换:
if (inspected.packageSha256 !== expectedPackageSha256) {
throwIpcError('GHOST_FILE_INVALID', '插件文件在确认后发生了变化,请重新选择并确认');
}
缺失的是对称的另一侧 —— 已装侧没有等价的 revision 校验。
三、加固需求
- 安装时由宿主落一份 approved manifest(含安装 receipt 与 revision),存放位置不在插件目录内、不可由插件源码流程直接改写。
- 更新弹窗的旧版本与权限差异一律以 approved manifest 为基准。
- 运行时能力/授权判断改用 approved manifest,不直接信任可变的 live
ghost.json。
packGhostDir 增加安装目录及其子目录拒收,基于 realpath,覆盖软链接、目录联接与 Windows 路径大小写等绕过方式。
ghosts:inspect / ghosts:update 携带并校验 expected installed revision;不一致即作废并要求用户重新确认。
- 插件信任元数据(
.cindy-trust.json)同样纳入宿主侧保存或加签校验。
- 修改已有插件时,Forge/Agent 先将插件复制到会话 workdir 形成独立工作副本。
- 补回归测试:版本差异、权限差异、状态竞争、软链接、旧安装兼容、Windows/macOS 路径。
- 同步更新 Forge 编写手册与相关工具契约,明确安装目录不能作为源码目录。
四、环境
- 报告者复现环境:Cindy Desktop 0.1.10,Windows 10 Pro 22H2 (Build 19045.6456, x64)
- 维护者代码定位:
main @ 39afc9b(macOS)
- 复现载体为报告者本地自制、未对外分发的插件,不含真实用户数据、令牌、凭证或内部服务地址
五、致谢
感谢 岳巍 通过 feedback@cindy.app 私密报告本问题,并在维护者完成影响确认前主动遵守协调披露约定。
复现细节、完整调用链与影响评估在私密渠道继续跟进;本 issue 仅用于公开跟踪上述加固项的落地。
来源标注 / Provenance
feedback@cindy.app一、报告者原文(节选转录)
关于流程代表性,报告者的说明(原文):
报告者的预期结果(原文):
二、架构缺口(维护者确认,
main@39afc9b)报告者的判断成立。核心是一句现有设计注释(
apps/desktop/src/main/cindy-brain/GhostManager.ts:86):即:宿主不保存任何独立的、经用户批准的 manifest 副本。插件安装目录中的
ghost.json是唯一事实源,而它是可变的、不在宿主保护范围内的普通文件。这一个根因派生出三处依赖:
apps/desktop/src/renderer/cindy-brain/installFlow.tsx:102,from取自installed.manifest.version,installed来自现读安装目录。installFlow.tsx:98,diffGhostPermissionItems(installed.manifest, manifest)的左值是同一份现读结果。manifest.slots/manifest.network等声明的路径同样经GhostManager.list()取得,未与已批准状态解耦。此外
apps/desktop/src/main/cindy-brain/forge.ts:528的packGhostDir(dir)只接受目录参数,无路径归属校验;而同文件:394的scaffoldGhostDir已有完整的 realpath + 会话 workdir 收敛校验。两者护栏不对称。同样地,
ghosts:update(apps/desktop/src/main/cindy-brain/index.ts:3144)已用expectedPackageSha256防止候选包在用户确认后被替换:缺失的是对称的另一侧 —— 已装侧没有等价的 revision 校验。
三、加固需求
ghost.json。packGhostDir增加安装目录及其子目录拒收,基于 realpath,覆盖软链接、目录联接与 Windows 路径大小写等绕过方式。ghosts:inspect/ghosts:update携带并校验 expected installed revision;不一致即作废并要求用户重新确认。.cindy-trust.json)同样纳入宿主侧保存或加签校验。四、环境
main@39afc9b(macOS)五、致谢
感谢 岳巍 通过
feedback@cindy.app私密报告本问题,并在维护者完成影响确认前主动遵守协调披露约定。复现细节、完整调用链与影响评估在私密渠道继续跟进;本 issue 仅用于公开跟踪上述加固项的落地。