状态:权威开发规则(authoritative) 读取时机:新增或修改插件(
.cindy)的运行时、沙箱、权限、能力声明、面板供片、 网络/凭证/文件交接,插件作者可见的身份卡、管子协议、打包与编写手册,或批准状态、 安装布局、指纹格式等存量安装读得到的任何东西之前
本文治理 Cindy 中运行的插件——以 ghost.json 为身份卡的 .cindy 包。它约束三件
互为支撑的事:运行时权限安全(宿主如何隔离并授权插件)、存量插件兼容(升级
不得让用户已装的插件失效或需要重装)和作者契约同步(改了作者可见的能力,必须同步
编写手册与校验)。Electron 进程与协议安全另见
electron-security-and-process-boundaries.md,
媒体字节交接另见 media-storage-and-protocols.md,插件在
产品中的定位见 ../product-rules/core-product-principles.md。
术语:产品与对外措辞统一为「插件」,
.cindy即插件包;不再使用旧的概念称呼。 代码标识仍沿用历史Ghost/cindy-brain命名(目录cindy-brain/、GhostRuntime、ghost.json、cindy-ghost://、ghost_forge_guide等),与产品术语「插件」指同一事物; 引用代码时照实使用这些标识,不因措辞改写。
增量适用原则:运行时沙箱与权限的安全不变量(下节 2–4)与存量插件兼容红线 (下节 5)对所有触及相关路径的改动都生效,不因是存量代码而豁免。作者契约同步 (下节 6)在改动命中作者可见契约时触发;不要求为统一形式专项重构无关存量。
| 内容 | 权威来源 |
|---|---|
| 编写手册(作者唯一教材,现拿现读) | apps/desktop/src/main/cindy-brain/forge.ts 的 FORGE_GUIDE,经 ghost_forge_guide 工具下发 |
ghost.json 身份卡字段与校验 |
packages/plugin-protocol/src/manifest.ts 是跨消费者协议正本;apps/desktop/src/shared/ghost.ts 是 Desktop 运行时 validator。除下文登记的 Desktop-only 能力外,两端必须同步维护 |
| 管子协议类型 | apps/desktop/src/shared/ghost.ts(cindy.send / cindy.onHostMessage 类型) |
| 打包限制 | apps/desktop/src/main/cindy-brain/forge.ts 的 packGhostDir |
| 运行时、沙箱进程与生命周期 | apps/desktop/src/main/cindy-brain/runtime/GhostRuntime.ts、GhostManager.ts |
| 安装事务状态、内容摘要与技能快照 receipt | apps/desktop/src/main/cindy-brain/ghostInstallReceipt.ts,状态投影见 shared/ghost.ts 的 GhostInstallApproval |
| 能力实现(网络/通知/确认/文件系统/技能/宿主等) | networkSlot.ts、notifySlot.ts、badgeSlot.ts、confirmSlot.ts、fsSlot.ts、cindySlot.ts、skillSlot.ts、agentSlot.ts、errandSlot.ts;持久作品库见 plugin-library-storage.md,主实现在 libraryVault.ts、librarySlot.ts、libraryDbCore.ts |
| 面板供片、注入主题 token 与协议 | apps/desktop/src/renderer/cindy-brain/ghostPanelTheme.ts、cindy-ghost:// 分支 |
| 插件详情能力说明 UI | apps/desktop/src/renderer/features/plugin/GhostPluginDetailView.tsx |
| 远程/手机版能力准入白名单 | packages/device-link/src/allowlist.ts |
| 行为与安全不变量 | apps/desktop/src/main/cindy-brain/__tests__/、forge.test.ts |
文档与实现冲突时以代码为准,但必须在同一改动内同步修正本文与手册。
.cindy是以ghost.json为身份卡的插件包,现行唯一形态为kind: 'chip'。- 代码目录与运行时使用
cindy-brain/Ghost命名,不得重新引入已退役的 cartridge 声明型兼容层。 cindy-/filo-/xd-是官方保留 id 前缀,第三方插件不得占用;前缀正本见apps/desktop/src/shared/ghost.ts的GHOST_OFFICIAL_ID_PREFIXES。
- 新插件统一使用
schemaVersion: 3,并填写插件实际依赖的首个 Cindy 正式版本作为minCindyVersion。Manifest schema 不设置统一的 Cindy 版本下限;具体版本只属于插件包元数据。 v3 没有slots;tools、card、panel、mainView、subscribe、skill、cindy、agent、node、network、preview等顶层字段本身就是插件贡献项或自主 Host 能力的直接声明。 - 无配置的布尔能力只接受字面量
true:notify、badge、confirm、fs、library、sessionContext、pick、workspace。不用就省略,写false是无效清单。 card: {}与agent: {}分别表示基础卡片能力和由真实用户点击触发 Agent 回合; 其它对象型能力必须至少包含一项真实能力,不能用空对象占位。- v3 未识别的顶层字段,以及能力对象中的未知扩展字段、动作和订阅事件,必须原样保留, 但当前 Host 不展示、不授权、也不因此阻止安装或发布。 未来 Host 识别该字段后,才按正常的能力展示和运行时守门链路启用。不能把 未知字段猜成现有权限,也不能让它意外获得能力。
agent.tasks另需 Host receipt 的显式任务能力批准事实;旧 receipt 没有该事实时, 只在首次任务能力调用通过既有宿主权限界面确认,原插件的启用态、其它能力、凭证和 设置保持不变。拒绝、窗口不可用、账号或安装版本变化均不授予任务权限;同能力更新 保留既有明确批准,移除该能力后不保留。清单或旧未知字段本身不能补写批准。- Agent 在途调用不需要重复登记具体操作。 插件工具是否执行由当前
ghost_call的 Cindy Agent 授权决定;普通网络和当前工作目录操作使用主机 下发且仍在途的callId。随包代码与 CLI 继续使用已有 Node 工作进程,顶层node字段只声明这一运行形态,不预登记具体命令。不得为具体 CLI、域名或路径 新增 Slot、manifest 字段或客户端特例。只有插件要在 Panel、订阅、常驻进程、 后台任务等脱离当前 Agent 调用的场景自主使用 Host 能力时,才需要 对应直接字段。需要 Cindy 尚未实现的真正新 Host 服务时,才提高minCindyVersion并增加客户端实现。 schemaVersion: 2只作存量兼容。Desktop 在验证边界把已知slots显式映射为同名的 v3 直接字段;运行时只消费规范化后的直接字段。未知但格式合法的 v2 slot 不阻止安装, 运行时也不授予任何能力。未来支持必须新增明确映射,不得按名称猜测。- v2 的空
slots和历史零能力声明可继续安装,规范化时直接丢弃。v2 → v3 更新按规范化 后的直接能力比较;只删除slots、能力等价时不算新增权限。 - 安装/更新前向用户确认插件声明的权限(2026-09-25 用户裁决,取代此前「安装不设能力
确认弹窗」的做法):首次安装一律确认;更新只在新版本权限变多时确认;服务端
defaultInstall下发的插件首装与后续更新都不确认。判据、入口与落位前复核见第 3.1 节。 确认只决定这次安装/更新是否进行,不是运行期授权:插件自主 Host 能力仍须在 manifest 如实声明、在插件详情展示,并由 Host 运行时强制守门;动态资源与 Agent 在途操作不重复 建权限实体,继续复用 Cindy 既有 Agent 授权系统,不新增 grant/申请状态机。
- 每个运行中的插件使用独立 Electron 沙箱进程与按 owner × plugin 隔离的内存 session
partition。同插件的
settingsHtml、panel 与逻辑页继续共享该 partition,保留 browser storage 与BroadcastChannel契约;沙箱禁止直接访问 Node、宿主文件系统和通用网络, 唯一例外是第 4 节明确限定的 HTTPS 图片资源。 - 插件只允许读取自身安装目录内、经安全相对路径校验的静态资源,不得越权读取其它目录。
- 逻辑页只能经最小
contextBridge管子申请主机能力;面板 webview 保持零特权桥。 - 主机按
webContents绑定反查真实 ghostId,不信任 sender 自报身份。 - 沉睡、抽离和主机退出必须终止对应沙箱;沙箱崩溃只由
GhostRuntime收敛,不得带崩 主应用。
- 先判断谁在执行,再判断授权边界:
- 当前 Agent 在途
ghost_call是否执行插件工具,由该会话既有的 Agent 授权决定。 普通网络和工作目录操作以 Host 下发的严格在途callId绑定会话;随包代码与 CLI 仍由已有 Node 工作进程执行,不新增 Host 进程协议或具体命令登记。 - 插件脱离当前 Agent 调用的自主 Host 能力,必须先在 manifest 以对应顶层字段 直接声明,通过校验,在插件详情中如实展示,再由 Host 代码强制守门。
- 当前 Agent 在途
- Agent 调用必须同时满足:
callId尚在途、绑定同一插件、绑定本地会话。已交卷、 他插件、scheduler 脚本、Panel 和后台调用都不得借此绕过自主能力声明。 对未声明公网目标,Host 必须在每一跳完成代理选择与 DNS/IP 守门后、真正 dispatch 前再次复核同一callId;直连与系统代理都只能连接该次守门确认的 IP,代理不得 重新解析插件提供的域名。选中代理不构成私网授权;无法证明由当前代理生成的 fake-IP 仍按特殊用途/私有地址拒绝。 - 安装/更新确认只决定这次落位是否进行,不以弹窗点击作为运行期能力授权事实; prompt 和前端展示都不构成运行期安全边界。确认结论由 Main 在安装锁内按真实包复核, 不能由 Renderer 或 Agent 自报(第 3.1 节)。
- 新增或修改能力声明时,除同步编写手册与校验(下节 6)外,还必须同步 shared 类型、
preload/host handler、详情能力 UI(
GhostPluginDetailView.tsx)、错误边界和测试。
插件普通任务创建的 Worker 目录由宿主校验:允许当前任务目录及解析符号链接后仍在其中的子目录、插件 AI 配置目录、用户通过目录选择器授权的确切目录。Library 绑定本身不授予 Agent 工作目录权限,协同计划也不构成目录授权。登记计划和实际创建 Worker 均复核授权;账号、配置或任务归属发生变化时拒绝继续创建。
这些目录授权只标识任务所在电脑的资源,不授权另一台电脑的同名目录。插件自有 Lead 不支持通过 executionDeviceId 新建跨设备 Worker:有无团队计划均在目录探测、槽位预留和远端创建之前拒绝。普通用户任务的跨设备 Worker 及插件既有 Worker 路径不变;不新增插件批准、配置或迁移。
同一次 Worker 创建沿用最初的账号与任务归属,在启动 Session、续租完成后、写入 Worker 前再次复核。
复核失败沿用既有尽力清理路径(关闭、移除运行缓存、归档 Session 与释放预留),不派发初始任务;新团队沿用创建失败收尾。
卸载后用户新发起的普通 Orca 创建仍可使用旧任务,但在途插件创建不得因卸载转成普通创建。
此检查不构成跨持久化与派发的事务,也未新增跨账号数据库补偿;实现及回归见
apps/desktop/src/main/maker-ipc/orcaWorkerCreationService.ts、register.ts 与 __tests__/orcaWorkerCreationService.test.ts。
插件任务目录在文件系统查询前拒绝 UNC/device 路径,并先按上述宿主目录记录核验候选路径;不通过探测未登记别名发现授权目标。本地 Windows 盘符长路径保留支持。解析后仍复核真实目录;这不构成执行隔离,也不能阻止已授权目录内的链接置换。
- 首次安装只来自明确依据:用户导入本地
.cindy、明确要求当前 Agent 调用ghost_forge_install、用户点击某个市场条目的安装、当前 Agent 按用户请求与既有操作授权 调用ghost_market_install安装选定的缺失插件,或服务端为当前 owner 下发defaultInstall。安装成功默认启用。 - 安装/更新确认(
shared/ghostInstallConsent.ts判据,main/cindy-brain/ghostInstallConsent.ts两段式实现):- 判据:「权限」与插件详情页权限区同口径,即
ghostPermissionItems中除 Agent 工具外的 全部条目。首次安装展示全部权限并确认;更新相对当前已装版本按diffInstalledGhostPermissionItems比较,新增条目、同 key 条目内容变化或内置 OAuth 客户端变化才算权限变多,只删除不算; 已装版本没有有效批准基线时按全部权限新增处理。 - 入口与确认界面:插件页、拖入、双击
.cindy与设置页导入由发起窗口弹确认框 (GhostInstallConsentWindowBridge→GhostInstallConsentHost,只投给、也只接受发起安装 的那个 webContents);Agent 调用ghost_market_install/ghost_forge_install不论任务权限档 (含 Full Access)都向调用所在任务投宿主权限确认卡(permission交互 +hostOwnedConfirmation: 'plugin_install',经requestHostInteraction路由),桌面对话、 手机远控与 IM 渠道卡都能处理,旧版手机无需升级。用户拒绝返回MUTATION_CANCELLED, Agent 不得自行重试;确认界面不可用时 fail closed。 - 两段式:确认在任何安装锁、来源锁与 owner 租约之外求得(等待期间不阻塞其它插件);
落位前在按 ghostId 的安装锁内用即将落位的真实包、现读受体 receipt 与确认前钉住的包摘要重算
(
assertGhostInstallConsent),确认后权限面、已装版本、受体 receipt 或包字节变化就拒绝; 用户确认过的更新不能因为受体已被换成权限已覆盖候选包的版本而静默落位。自定义市场在 来源 cache-path 租约内只打包,释放租约后再求确认;提交时仍复核来源。Forge 在 packing 租约内捕获 owner 并传入装入入口,等锁结束后若账号已切换则拒绝取租约。每条装入路径都 必须显式交出确认策略(prompt/automatic/exempt)或锁外求得的结论,签名上必填, 新增路径无法漏掉。 - 服务端
defaultInstall插件(首装、组织默认接管与之后的自动更新)按下发方决定, 策略为exempt,不向用户确认。豁免只绑当前用户对该条目的默认下发资格:目录仍标defaultInstall、但用户已退订后再自行从市场安装的,扩权更新必须确认。
- 判据:「权限」与插件详情页权限区同口径,即
- 市场安装账本是后续更新来源的唯一事实:服务端市场按
pluginId + releaseId路由, 自定义市场还必须匹配sourceKey;已装目录的原始ghost.json字节 SHA-256 必须与账本 一致。旧记录缺少 raw 字段时,Host 只能按已发布的 legacy digest 编码核对同一份受限读取 的字节,命中后原地补字段且不改updatedAt;raw 字段已经存在但不匹配时必须 fail closed, 不得用当前目录覆盖基线。manifestDigest保留旧语义供降级客户端读取,不能改写成 raw SHA。 同 id 来源冲突或目录漂移通常不得被自动覆盖,只能由用户显式选择替换来源。唯一受控例外是: 当前组织下发defaultInstall、组织与合法非空前缀都精确匹配、同 ghost id 在目录中唯一时, 可以接管无有效市场来源的普通本地安装,或修复同一目标pluginId的坏 market / legacy-adopted 记录。接管仍要求现有 receipt 已批准、未显式卸载退订、插件不忙,并在落位前于 ghost id 锁内 重读 owner、来源、批准与退订事实;不得覆盖有效的公开/其它组织市场路由或 Git/本地自定义 市场路由。ghost_forge_install的首装与更新一律记录agent-forge,作为作者本地自测保护, 不进入自动接管;普通.cindy导入仍是manual,不享受这项保护。自动接管不得复用用户手动 换源的退订语义,不写markRemoved或 default-install opt-out。普通本地/Forge 换源只把 旧来源记录置为installed=false,不得新增或清除 opt-out;只有用户显式卸载才写 opt-out。 - 仍匹配稳定来源的已装插件自动更新,不限 public/organization;更新保持现有启用状态,
不弹成功 toast;插件正有调用、派活或 Cindy 工作时跳过,下一轮重试。权限没变多的更新
静默完成;新版本权限变多时(按下载后的真实包判定,策略
automatic)放弃本轮、不记失败、 不退避,同一 release 在当前进程内不再重复下载,旧版本照常可用;市场条目带updateRequiresConsent,插件页标出「需确认新权限」,用户点更新时再弹确认。Renderer 目录走延后对账时,记下新 hold 后立即通知重投影,不等无关前台刷新。自定义 市场暂停记录同时钉住 release 与包内容指纹,同版本包被改掉后必须重新判定。仍具备 默认下发资格的服务端defaultInstall插件不受此限,权限变多也静默更新。 - 服务端市场按客户端版本投影最近发布、曾上架且仍有效的兼容
Release;current 不兼容时回退兼容历史版本,没有兼容版本时不展示。Desktop 信任该投影,
不再用
minCindyVersion二次筛选、跳过或弹兼容性确认。自定义市场和本地.cindy采用 同一安装策略:minCindyVersion是发布/发现元数据,不是客户端安装授权或确认闸门。 真实包身份、Release 摘要、能力上限、Manifest schema 与当前 Host 是否能解析仍按各自 安全边界校验,不能把包内容异常降级成“仍要安装”。 - 自动对账在 owner 稳定后的启动/登录切换、应用回前台、系统唤醒和 30 分钟周期触发。 不新增持久化任务队列或“是否自动更新”设置;卸载写下的 opt-out 继续阻止默认安装复活。
- 同一 release 自动更新失败后,在当前进程内按 owner、来源路由和 release 做指数退避 (5 分钟起、最长 6 小时),日志记录失败次数与下次重试时间;来源发布新 release 时立即 解除。忙碌跳过不记失败,用户手动重试不受退避限制。
- 服务端包必须通过 release SHA/大小及 id/版本校验,安装与运行时能力以真实包内的
Manifest 为准,不将服务端目录的 Manifest 投影作为第二份能力上限。
自定义市场以发现并规范化的 manifest 为能力上限。超出能力上限按包内容不一致拒绝并留待
来源修复,不转成用户审批流程;上限内的新版声明仍按上一条判断是否需要用户确认。能力上限按 Host 实际消费语义
比较:真实包不得从无到有增加
settingsHtml设置 WebView 或扩大固定settingsHeight; OAuthscopes换序或取子集属于收权,其它 OAuth、凭证标签与注入字段仍须保持在清单上限内;setup.requires只能保留或删除市场已有的完整需求组,不能增加组、收紧anyOf或更换引用; 真实包同名工具的parametersJSON Schema 必须与市场规范值一致(仅对象键顺序可不同)。 - 本地
.cindy没有稳定来源,不自动更新;用户再次导入同 id 新包时原位更新(权限变多时 先确认),由 Main 保持当前启用状态。 - 市场下载与本地安装使用一致的 Node 包体边界:压缩包最多 128 MiB、解包总量最多
256 MiB、最多 2048 个条目;普通沙箱包仍为 8 MiB/32 MiB/256 个条目。
下载前尚未识别真实包类型,按 128 MiB 限流;下载后由共用安装器按真实包类型校验。
市场下载采用 120 秒总超时和 60 秒无进展超时,任一触发即中止;分块写临时文件并校验
完整大小和 SHA-256,失败清理本次临时文件,不替换现有安装。错误提示区分下载超时、
网络失败与安装包异常;服务端负责包体大小发布准入,客户端保留防御校验而不另设体积提示。
实现与回归见
apps/desktop/src/main/plugin-market/download.ts及同目录的__tests__/download.test.ts。 - Host receipt 是安装事务和运行完整性的状态记录,不是一次交互式“能力授权”。合法的安装/更新
事务(已按上文求得确认)写入 receipt,钉住 canonical manifest、trust、启停态与随机
revision;ghostInstallApprovalToken()只是 Renderer 与 Main 之间防止并发漂移的前置条件,不是权限凭证。 没有或损坏 receipt 的存量安装优先走自动迁移/对账,不能改成逐插件能力确认弹窗;安装确认只作用 于新的安装/更新事务,Cindy 升级后已装插件不因此需要重新确认(以当前已装版本为基准)。 - receipt 不等于“安装目录从此不可变”的证明:普通
packageSha256仍是安装时点来源指纹; 真正越出沙箱的技能继续使用 receipt 绑定的字节指纹与 Host 状态根快照。不要把审计字段误写成 全量运行时内容校验,也不要把安装确认当成完整性守门而删减现有校验。 - Forge 的源码区与 Host 受管根互斥。
ghost_forge_scaffold/ghost_forge_pack/ghost_forge_install的目标必须是独立作者目录,不得是安装根或状态根;按 realpath 挡住大小写折叠与软链/junction 别名。会话工作目录内直接放行;工作目录外走与ghost_call过户相同的会话权限路径(本地 Full Access 自动放行,Auto 交当前会话 AI 审阅,Ask 弹确认卡,远程/缺会话/查询失败 fail closed),禁止在 Host 已放行 后再因目录边界悄悄硬断。ghost_forge_pack只负责校验与打包;只有用户明确 要求后调用独立的ghost_forge_install才安装或更新,不因 scaffold/pack 成功而隐式安装。 - 存量插件技能投影在当前账号的
<状态根>/agent-skills,不写~/.agents/skills或~/.claude/skills。Claude 使用每插件独立的本地 plugin(命令带命名空间), Codex 通过 Cindy 的CODEX_HOME/skills,Pi 通过显式--skill加载;均保留技能 停用与 Bot 白名单。旧全局链接仅在能证明属于 Cindy 时迁移删除;用户文件与外来 链接保留。账号切换撤销私有投影与遗留全局投影。 skill是唯一越出沙箱的能力:技能指令由主 Agent 以用户全部权限执行、全局 生效、不随 workdir 级停用隐藏。其安全边界是声明一致性(manifest 里的 name/description 必须与 SKILL.md frontmatter 逐字一致,skillSlot.ts的checkSkillMdConsistency是唯一裁判,打包与装入、以及 Host 固化快照三侧共用;注意它只 校验 frontmatter 的 name/description,正文与辅助文件不在它的判据里)+ Host 固化快照与字节指纹(安装/更新事务提交时把技能目录逐字节拷进<状态根>/skill-snapshots/<id>/<revision>,只收普通文件,同时把逐 item 的内容 指纹钉进 receipt 的skillContentSha256——装入/更新时该指纹取自.cindy包的 内存投影(inspect 时已被packageSha256钉住的那份字节),不从已发布的可变安装 目录首读:publish 与首次 hash 之间被换的字节应当在快照对账时被拒,而不是被首读钉成 固化基线;安装事务校验的 SKILL.md 必须就是 Agent 之后 读到的那份,所以 Cindy 私有技能投影的链接指快照而不是可被改写的cindy-brain/<id>/<dir>。 快照缺失需要从安装目录重建时,顺序本身就是安全性质:先把字节复制进状态根的 临时目录,再对临时目录里那份即将成为快照的字节做全部权威校验(尺寸上限 → 指纹逐字节比对 → frontmatter 一致性),通过才 rename 就位。不得改成"先校验安装 目录、再复制"——安装目录随时可被同权限进程改写,校验与复制各读一次就有一个可换 字节的窗口,复制出来的快照可能不是被校验过的那一份。同理,复制前对安装目录做的 任何预检只是"早失败"优化,不是安全边界;尺寸上限必须排在算指纹之前——上限要在 权威路径上真正生效,且不为一份注定被拒的字节先付一整趟读取成本。指纹计算一律流式 喂入、不整份读进内存(技能目录里除 SKILL.md 之外的文件没有尺寸上限,整份读会被一个 塞进来的超大辅助文件撑爆)。只靠checkSkillMdConsistency拦不住"frontmatter 不动、改写正文或塞 辅助文件",那会把一份未经合法安装/更新事务校验的指令在一次启用里固化成 宿主快照并挂载到 Cindy 会话。对不上一律 fail closed,要求重新安装合法包,不许就地自愈成新固化状态;skillContentSha256因此是 运行期判据,与只作审计用的packageSha256不同,且必填——留"字段缺失就跳过 校验"的可选口子等于给漂移开一条绕过路径)+ 链接对账(reconcileGhostSkillLinks只增删"目标落在 安装根或批准状态根内的 symlink/junction",绝不触碰真实目录与外来链接; 启用挂链、停用/卸载撤链、断链自愈)。快照目标带 revision,因此每次更新都换目标、 靠对账重指,旧 revision 在 receipt 提交后回收。改动技能落链、命名 (<id>--<name>,name 侧禁--)、快照或对账判据前,必须先读skillSlot.ts头注释并保持上述不变量;approvalStateRoot是必填项——漏给会让指向快照的活链接被 判成外来链接而永不撤链,停用/卸载后技能仍对主 Agent 生效。- 收敛方向不对称:启用需要有效批准状态,停用必须永远能成功。停用是安全方向,不 能因为快照缺失之类的环境问题把插件卡在"既不能用也不能关"。
- 「插件内容目录怎么读」只有一份判据:
ghostContentTree.ts。 条目类型判定 (classifyGhostDirEntry,一律lstat、链接与非普通条目显式归类,不信 Dirent 的 类型位)、清单相对路径的逐段解析(resolveGhostContentPath,中间段是链接一律拒)、 内容树收集与指纹格式(collectGhostContentFiles/hashGhostContentFiles)都在这里, 各调用方之间的差异只允许以显式策略参数表达:点开头条目算不算内容 (技能目录include/安装目录与种子skip)、非普通条目是throw(授权判据)还是flag(对账判据——收敛动作是重新播种而不是拒绝)。- 理由是实测的复发史:同一条判据曾经在技能指纹、快照拷贝、安装目录漂移指纹、随包
种子指纹、种子复制、Forge 打包收集六处各写一遍,还有五处各自
path.join后再判 一次类型,分别用 Dirent 类型位/lstat/stat/realpath 钳制实现。于是每轮审查都 能在其中一处找到没覆盖的角落,补一处、下一轮换另一处。 - 新增任何"读插件内容目录"的代码一律从这里取判据,不要就地
readdir+isDirectory()或stat直读。只lstat最终段等于没判:中间段被换成软链/junction 时 OS 会 静默穿透,最终段报的是"真目录、非链接",字节却来自插件目录之外。 hashGhostContentFiles的摘要编码必须保持无歧义 framing(当前为cindy-ghost-content-v2+ UTF-8 路径长度前缀 + 每文件摘要),不得恢复成path + NUL + bytes + NUL;文件内容本身允许包含 NUL,分隔符编码会产生不同文件树 的等摘要。该编码升级同步 bumpGhostInstallReceiptschema;旧 receipt 必须 fail closed 并按第 5 节迁移,不能拿旧摘要继续授权。编码 bump 属于第 5 节的存量兼容 场景:先按旧编码核对旧摘要与当前安装字节,对得上就原地重算成新编码并升级 receipt(安全水位与旧版本已提供的保证等价),只有对不上(真漂移)才 fail closed 并 要求重新安装合法包——不得把一次纯格式升级直接变成全体用户的重新安装操作。- 同理,"源目录与受管根的包含关系"必须双向判(既不能落在受管根内,也不能是受管
根的祖先):单向判定下只要在 owner 数据目录里放一个
ghost.json,递归打包就会把 已安装插件字节、批准 receipt 与技能快照打进.cindy。 - 随包种子是第一方输入;发现链接、junction、FIFO 等非普通条目必须整颗跳过并告警, 不得在复制时静默丢弃后继续写批准 receipt。
- 理由是实测的复发史:同一条判据曾经在技能指纹、快照拷贝、安装目录漂移指纹、随包
种子指纹、种子复制、Forge 打包收集六处各写一遍,还有五处各自
- 自主 network 只允许 manifest 白名单域名;凭证由主机保险库注入,无明文读回
给沙箱。Agent 在途调用可凭严格
callId访问未预声明的普通 HTTPS 地址,仍须通过 主机 URL、SSRF、超时、体积与重定向守门。此途径不注入任何未在 manifest 中声明且 未命中目标 host 的托管凭证。 source: "gh-cli"是只为官方cindy-github保留的宿主凭证来源:Host 优先读取 本机gh auth token,不可用时才回落到同 key 经/secrets保存的 PAT。两种 token 均只在 Main 的 networkSlot 或受信本机账号检查中注入api.github.com的Authorization: Bearer请求头,不得进入插件、Renderer、Agent、KV、日志或 Node Worker。本机账号检查仅允许固定 GET/user,拒绝重定向,限时 15 秒;只返回 连接分类、用户名和来源,不返回响应原文。插件设置页只能读取hostAvailable、hostManagedSetup布尔与备用 PAT 的saved/tail状态。该来源 不允许exchange或setup.requires引用,第三方插件不得声明。source: "oidc-token"是 Host 托管的短时 Cindy Connection JWT:只对当前企业 Membership 生效。资格有两条默认基座:当前组织的 Plugin Market organization 安装记录仍有效、 且安装目录ghost.json的 raw SHA(旧记录迁移前为集中 legacy digest)与记录一致; Manifest 与身份必须来自同一次受限读取,禁止分两次读取后分别校验与消费;或企业作者显式使用ghost_forge_install安装、在提交前核对插件 id 与精确注入域名,且插件 id 命中当前组织前缀。手动导入不取得 Forge 作者资格。另有一条点名例外:ghostId精确等于mivo-canvas的组织成员本地安装, 在已装 manifest 声明的精确oidc-tokenhost 仅为mivo-canvas.dsworks.cn时可解析 audience;其它本地插件、个人账号、 通配 host、其它精确 host 仍不签发。若该插件已有市场 organization 记录(含installed:false的卸载残留),不得走白名单捷径,必须仍走 digest 校验。市场账本损坏、schema 不认或该 ghostId 记录校验失败时 fail-closed,不得当成「无记录」走例外。Host 根据当前组织和插件 id 推导 audience。 插件和 Node Worker 都不能读取或保存令牌。声明必须固定使用Authorization: Bearer {value}并显式列出非空inject.hosts;其中只允许精确域名, 不允许通配。实际目标必须精确命中这份可信 manifest 声明的服务域名才会签发和注入。它没有用户输入、url、exchange或setup.requires配置动作。Connection JWT 请求遇到 401 时,仅 GET / HEAD / OPTIONS 可自动换令牌重试一次;非幂等请求只作废缓存,不自动重放。- 插件 setup 的完成状态只由 Host 读取真实持久化状态后判定。简单的
source: "user"Secret 可由 Host 在聊天 Setup 卡中生成inline_form并直接写入 保险库;插件详情页的settings.js仍可通过/oauth、/kv、/secrets、/connections完成正常保存。两条路径都不得自行向聊天卡回调“已完成”,Renderer 也不得以轮询或BroadcastChannel事件替代 Host 判定。 - Agent 可编排 setup 卡片的说明与步骤,但只能引用 Host 下发的 requirement / action;
插件身份、字段 schema、字段与存储目标的绑定、Action 执行、完成状态和原
ghost_call恢复均由 Host 掌控。Secret、Token、OAuth code 和连接凭证不得进入 Agent、Ghost、interaction / pending snapshot、会话历史、日志或分析事件。内联 Secret 只允许短暂存在于本地 Desktop 输入组件和一次性的 trusted Renderer → Main 专用 IPC; 不得走通用 interaction response、通用 device-link invoke,也不得写入 Renderer store。提交成功、取消、request / revision 替换和组件卸载时必须清空。远程普通 user Secret 的窄例外仅为下述签名输入桥,不提供保险库读取或同步。 - 远程设备插件 OAuth 的 Desktop 回调桥 是独立的 Host 对 Host
授权事务,只承接当前卡片的已声明 OAuth action。URL/code 必须在两个 Main 间加密,
不能暴露给 Renderer、Agent、插件或通用 invoke;token 仍由目标 Host 现有账号管理器交换保存。
通用设备码、浏览器确认和 CLI PKCE 回调由同一类型化适配层承接,仍须精确任务/插件/目标
绑定;可信 Node 的私有 CLI callback 只回 bootstrap Promise,不经 stdout、沙箱或模型。
Node 本来拥有系统用户权限,这不是隔离恶意 Node 的保证;不放开任意端口/URL 转发。
新 Node 授权卡须以
cancelWithCall:true和当前callId显式启用;只带旧调用编号 不取得授权能力,也不改变旧 RPC/后台子进程生命周期。任务 Stop 后不得迟到启用, 取消只回收该调用所属子进程,不停止同一 Worker 中其它调用。 普通 user Secret 可由remoteSecret:true卡片通过专用本机plugin-oauth:submit-secret加密送到目标 Host;必须先签名握手,并比较原 Host 的完整字段展示与用户看到的字段,绑定 设备/插件/action/revision,在最新声明与写入边界复验。只允许写既有 executor 解析出的 单个 Secret;OAuth、gh-cli、oidc-token 和账号 vault 不接受此输入。Bot/旧卡片无此标记, 不自动获得能力。原始值不进通用 invoke、事件、会话历史、日志或 store;不增加确认窗口。 用户要求的设备码卡片展示是窄例外:加密 device offer 中供用户手工输入的短时userCode可通过本机专用plugin-oauth:device-code接口投影到发起授权的自有顶层 Renderer。只在当前卡片组件内存显示;按 owner、窗口/frame、设备、插件、request/action 绑定,支持再次复制与重新打开 Main 保管的同一授权页。过期、终态或身份失效后清除。 它不包含 OAuth callback code、device_code、Token、完整 URL 或 state,不进入聊天 snapshot、模型、持久化或通用远控;不改变原授权卡片或新增确认窗口。 - Host 必须把每个未满足
any_of组的全部可执行 item 投影到卡片,Agent plan 不能隐藏合法配置路径。Renderer 统一按组展示选项并复用 Ask 卡片的正文限高与纵向 滚动,不得为 Brave、Tavily、Gmail 等具体插件增加分支。 network.secrets[].url可由 Host 作为 Setup 字段旁的辅助获取入口展示。该地址必须 继续满足 manifest 安装期的https、无内嵌凭证校验;它不是 Agent 文案或 plan 的一部分,插件也不能通过settings.js动态替换 Setup 卡地址。- 模型调用一律走 Cindy 统一通道,不允许插件自建绕过通道的推理请求。
- 插件是沙箱小程序,不等于 AI 代办。模型与任务相关能力必须分别描述:
- 快问快答(
cindy.text.oneshot)不创建任务、没有 Agent 工具;沿用其统一轻量模型通道。 agent.run承接已有用户任务的继续/新建/分叉,保留原任务的交互与权限语义; 真实点击票/后台关联授权不因共用模型校验而扩大。cindy.tasks创建并管理插件自己的普通任务,有独立的 tasks 声明、批准与持久回执; 不依赖旧 errand 权限,不把任务完成等同于取到一段文本。workspace.ensureSession只定位/创建工作区任务入口;不因创建入口就自动执行模型。agent.errand是旧的派活取件适配器,保留专属任务映射与结果回传,不作为其它能力的产品抽象。
- 快问快答(
- 用户界面、Agent 工具、伙伴和插件的新建任务共用普通 Session 创建入口。伙伴的完成回报、 时间线与信号留在伙伴层;插件按需查询结果,不默认唤醒伙伴或另一个 Agent。
- 插件详情「任务设置」是插件新建任务的共用偏好,普通任务、工作区新建与旧 errand 使用 同一配置。显式完整模型组合优先;缺省从宿主验证的发起任务继承,面板无发起任务则使用 当前新任务的完整选择,不优先翻找 Claude Code 的历史草稿。模型选择不授予权限或目录。 保存、新建和插件派发前复用普通独立任务的模型/供应商/Agent 准入,不另建目录或选择器。 连接状态不等于额度、网络及远端参数一定可用;执行失败如实返回,不静默换型号或账号。
- 插件新建普通任务的权限仅来自用户的插件设置或既有宿主确认流程,默认普通任务的
ask,允许 用户显式选择acceptEdits/auto,不新增bypassPermissions;不继承发起任务的权限。 旧 errand 适配器保留历史缺省plan与原 Agent 行为;已有任务及显式历史设置不自动改权。 不把历史plan值宣称为跨 Agent 的只读保证,也不为它改造各 Agent 的权限引擎。 旧存储文件名、键及 IPC 保持兼容,升级不丢配置、不重批权限。来源优先级、复用和恢复 详见 插件普通任务接口。 - 附件、媒体、目录和保存路径通过归属校验后的 grant/deposit/ledger 交接,禁止把
宿主绝对路径或不必要的字节暴露给沙箱。媒体字节须走
media-storage-and-protocols.md的统一入库。ghost_call的attachments/dir/save_dir在目标位于 workdir 外时,普通权限档 仍沿用现有确认与授权记忆策略;Auto 档把真实过户动作交给当前会话的 AI 审阅器, allow 逐次放行、block 返回原因、ask 或服务故障才交用户确认。Full Access 旁路则仅当 Host 能现读到本地活跃会话的运行时权限恰为bypassPermissions(Full Access)时自动批准。该判定不得读取启动期 MCP context 快照, 也不得回退可能滞后的 DBpermission_mode。businesssessionId不足以证明仍是同一内存 Session,必须同时匹配由 Maker 铸造、调用方不可覆盖的 instance identity;权限切换在途、 close/detach 已开始、会话缺失、实例不匹配、查询失败、远程会话均 fail closed。 对 Codex、Pi 与远端 Claude Code 这类进程外 harness,instance 只作为 opaque MCP route identity 写入 Host 生成的 loopback URL;桥接层必须将 URL identity 与注册表中的当前实例 严格比对,不匹配直接 401。 兼容旧客户端时,缺 instance 的 URL 可继续获得普通会话上下文, 但必须剥除 instance 能力,使 Full Access 自动交接继续 fail closed。 越界文件系统副作用(cindy-docs / 电脑工具的outside_workdir,以及 Forge 的forge_source)在缺少 instance、live grant 读不到或实例已失效时直接拒绝, 不得退回仅凭用户确认的放行;附件过户仍可确认。 自动批准须区分 Full Access 与 AI 审阅来源,不得伪装为用户点击,也不得写入人工目录授权 记忆。附件自动交接必须写独立ghost-tool-grant,不得写ghost-grant;这是回退兼容 边界——旧客户端只认识后者,降级时必须 fail closed,不能把新版自动交接误读成人工永久 授权。切回 Ask 后新请求恢复确认。在途插件操作统一沿用当前会话的操作审批:包括工作区 草稿创建、工作目录写入、媒体路径揭示,Forge 在工作目录外的打包/骨架,以及 cindy-docs / 电脑工具读写工作目录外路径, Full Access 不额外审批,Auto 进入现有统一审阅器, Ask 沿用原确认流程。越界路径的确认与执行绑定裁决时解析到的规范路径, 工作目录里的 symlink 不能把真实目标藏成相对路径。MCP 的prompt-each-time仅限制授权记忆,不得覆盖 Full Access, 也不得跳过 Auto 审阅。审批期间实例、权限或调用归属失效时,旧 allow 不可执行。 Plan 与操作审批档位正交:Host 副作用须先检查实时 Plan 状态,未知或切换中拒绝; Plan 切换代次也参与审批后和落盘前复核,不能以数据库镜像或切回原状态恢复旧授权。 一次性 Plan 的 UI 开关在发送后熄灭,不代表当前 Plan 回合结束;授权判定使用 Provider 的执行态,不能只读下一轮开关。Claude Code 本地/SSH 的 Full Access 短路同样不能 放行当前 Plan 回合里的非只读工具;显式批准计划后才恢复底层操作审批档位。 这不扩大本轮来源/执行范围、不改变跨主机路径归属,不替用户填写 Setup、OAuth、Secret 等必要信息,也不改变第 3.1 节的安装/更新策略。自主面板或后台调用不得借用前台会话权限。 实现与回归见 Session.reviewHostPermissionAction、 fsSlot.test.ts 和 ghostWorkdirGate.test.ts。dir/save_dir批准的是裁决时解析到的 canonical realpath 快照;出票必须使用该规范路径 并在票据库内重新解析核对,路径映射已变化时拒绝并要求重新确认。出票后真正读/写时仍须 再次核对根与目标真身;保存文件必须排他创建且不跟随最终 symlink,不能让短命票据留下消费期 TOCTOU。附件继续使用裁决前已读入的字节,不得在批准后重新跟随原始路径。 - 媒体模型接入分成两层:插件声明并封装业务能力,Cindy Core 提供低级目录和调用能力。
插件设置页/panel 只能通过同源只读
/media-models?type=image|video配置模型;Host 按 Gatewaymode切图片/视频大类,并结合插件cindy.image/video声明、Gatewaymodalities、Guide operation 与当前客户端协议支持度,只投影当前可执行模型。单模型 Guide 失败必须局部隔离,不能阻断其余模型或插件面板。响应仍只把归一化后的modalities.input/output原样交给插件,不得包含 Guide、endpoint、凭证或内部兼容判定; 付费请求前 Core 必须再次校验。新媒体生成请求只能由当前 Agent 调用永久 Coremedia工具发起,插件沙箱和 panel 不得直接提交、轮询或下载。存量cindy-request媒体代办仅作 兼容,不得作为新 Guide 模型的接入路径。插件工具通过现有tool-result返回普通 JSON, Agent 读取后自行决定下一次media调用;Host 不识别插件专用媒体意图字段,也不自动 转发。插件需要结果时,Agent 调用普通接收工具,并通过顶层ghost_call.attachments显式交接;Host 复用已有的通用授权链,将授权后的指纹注入args.attachments,绝不把本地绝对路径暴露给插件。插件自行保存业务状态和更新 UI。 Host 不自动回调插件,也不得新增画廊等插件业务语义。 - 所有插件 HTML 页面(
settingsHtml、panel、mainView 与逻辑页)都可以通过<img>或 CSS 图片直接加载任意 HTTPS 地址,这是唯一的页面网络直连例外。Host 统一生成包含img-src https:的 CSP,并由 owner × plugin session 请求闸把外部请求严格限定为protocol === "https:" && resourceType === "image"。HTTP 图片、fetch/ XHR、脚本、 样式表、字体、音视频、WebSocket 与其它协议一律不因此放行;同 ghost 的cindy-ghost://资源继续放行。该能力不新增 HTML sanitizer 或图片属性白名单;既有 CSP 继续阻止内联脚本和内联事件处理器,同包脚本行为不变。远程图片请求会向第三方暴露用户的 网络地址及完整 URL,作者不得把密钥、令牌或用户私密数据拼进图片 URL。session listener 按 owner × plugin 幂等注册;设置页与同插件其它页面继续共享 browser storage、IndexedDB 与BroadcastChannel。 - 面板供片与注入的主题 token 只用
ghostPanelTheme.ts白名单内的值,不扩大暴露面。 - 已下线功能仅保留通用迁移目录与历史清单兼容字段,不提供 Host 能力。
iosSimulator/ v2ios-simulator只用于旧安装记录往返和下线识别;升级前已装且启用的 用户在原插件位置收到迁移引导,旧运行时、MCP、技能入口不再加载。见feature-retirements.md。
红线:插件系统的任何改动,都不得让用户本地已安装、已启用的插件在 Cindy 升级 后变得不可用,也不得要求用户重新安装、额外确认、重新配置凭证或重新落一次技能。 升级后的默认结果只有一个:用户什么都不做,插件照旧能用。
- 判据是用户视角的可用性,不是代码路径没报错。 插件还在列表里但被标成「已停用」 「失效」「需恢复安装」,或者启用按钮点不动、技能链断了、面板打不开、 已配置的凭证要重填 —— 都算不可用。「fail closed 得很干净」不是通过条件。
- 触及范围(改这些就命中本节,逐条按"老数据怎么办"设计):宿主侧批准状态记录
(receipt 一类)的 schema/字段必填性/落盘位置、指纹与摘要编码、
validateGhostManifest的校验规则、能力字段与参数形态、技能快照布局与链接命名、 安装根与状态根路径、.cindy包格式、 管子协议消息形态、随包种子与内置插件 id/前缀、市场侧的 id 与版本口径。 - 新增校验或新增必填字段,默认必须自带迁移(backfill),不是自带拒绝。 老数据缺
新字段是升级前的正常历史状态,不是攻击证据,不得按篡改处理。迁移的判据是"能不能
从旧版本自己的安装验证事实重建出等价物":
- 例:receipt 机制上线前的安装,等价事实就在安装目录的
ghost.json/.cindy-trust.json/.disabled三份文件里(第 3 节所说的"旧版本兼容镜像")。 宿主必须能一次性读它们 backfill 出等价 receipt,用户无感。 - 迁移不得成为扩权或降级通道,这是它与安全不变量共存的前提:迁移只在该 id 从未有过 receipt(或 receipt 已判损坏)时发生,manifest 及运行事实原样取旧记录、 不做并集、不吞新增能力;迁移只重建旧版本已有的等价安装验证状态,之后的 manifest 变化只能由新的合法安装/更新事务固化;迁移来源必须记进日志,便于事后分辨 “正常安装写入”与“宿主迁移来的”。
- 迁移读不出(文件缺失、格式坏)或自相矛盾(镜像与内容互斥)时才 fail closed,落到下面的兜底义务。
- 例:receipt 机制上线前的安装,等价事实就在安装目录的
- receipt schema/指纹编码 bump 必须走「按旧编码核对 → 原地升级」,不是 fail closed 到用户重新安装。 纯格式变更时旧 receipt 的安装验证事实没有消失:先用旧编码核对旧摘要与当前 安装字节,对得上就原地重算成新编码并升级 receipt,只有真漂移才 fail closed。一次内部 格式变更不得变成全体用户重新安装。历史注:v1→v2(NUL framing 修复)没有专门的 v1 读取器,因为 v1 receipt 从未随任何构建发布、全网不存在;同时一次性 legacy 迁移会把 「已判损坏」的 receipt 从安装目录 backfill 治愈,效果上覆盖了该场景(有回归用例钉住)。 从 v2 起的任何 bump 都必须实现原地升级器,不得再引用本注作为豁免。
- 确实不可避免时的兜底义务(四条全部要满足,缺一条就不算做完):
- 能自动就别打扰用户:自动重建/自动重算/自动重新播种优先,且要能在下一轮启动 对账时自愈,不要求用户在特定时机点特定按钮。
- 自动做不到就必须明确提示:UI 要说清发生了什么、影响哪些插件、点哪里恢复,并 提供一次性批量恢复入口;不得只留"去市场逐个重装",也不得让它看起来像是用户 自己关掉的(与第 3 节的 UI 义务同一条)。
- 不丢用户本地状态:恢复过程不得清掉已保存的凭证/Secret、KV、per-plugin 偏好、 errand 配置、面板状态。恢复的是安装验证记录,不是用户的配置。
- 留回滚余地:新版本写出的状态被旧版本读到时不得当成损坏——未知字段忽略而不是
判
invalid,否则用户一旦回退旧版就再炸一次。
- 测试门槛:命中本节的 PR 必须有"从旧状态升级"的自动化用例——fixture 造出老布局 (无 receipt/旧 schema/旧指纹编码/旧目录形态),断言升级后插件仍列为启用、技能仍 挂链、无需用户操作。只测全新安装流程不算覆盖,这类回归只在存量数据上出现。
- PR 约束:命中本节的 PR,Description 必须写明「存量插件影响:无」或「有 + 迁移与 提示方案」,并说明上面的升级用例跑在哪。会让存量插件失效的改动与 mobile 冷更同级: 需仓库把关人针对该影响明确确认后才能合并,提交者身份不构成例外。漏迁移 = P0。
- 插件基座改动一律走白名单确认门。 「基座」= 所有已装插件共同踩着的那一层:运行时与
沙箱、来源/信任 receipt、能力声明、打包与内容判据、manifest 契约、装入与能力说明 UI、已装
列表投影(
main/cindy-brain/、main/plugin-market/、main/mcp-integrations/ghost.ts、shared/ghost.ts、packages/cindy-tools的 ghost 部分,以及 renderer 侧的installFlow.tsx/installErrorKey.ts/GhostPluginDetailView.tsx/useInstalledGhosts.ts/runtimeStates.ts/features/plugin/lib/ghostPluginViewModel.ts/features/plugin/lib/pluginMarketPresentation.ts)。命中即需放行人在 PR 上明确 Approve 才能合并,不看 diff 大小,也不因为「是 bugfix/纯技术改动」就放过——#1080 正是以fix身份、按纯技术改动被放过的。这条与本节前面的义务是一套:门只保证「有人 看过存量影响」,看什么按上面逐条对。纯粹改插件面板视觉、纯文案/locale 不算基座。 - 历史教训(本节的由来):2026-07-31 合入的批准 receipt 改造
(
ghostInstallReceipt.ts)把"无 receipt = 不构成运行授权"一次性作用到全部存量安装, 只给随包内置插件留了自动补批准的路,市场与本地安装没有 backfill 路径,结果升级后用户 所有非随包插件同时变成停用、必须逐个重新确认,本地包还要求重新提供原始.cindy文件(包已丢失就无从恢复)。安全方向是对的,落地方式把一次内部机制升级变成了全量 用户故障。
FORGE_GUIDE 是 agent 替用户编写插件的唯一教材,由 ghost_forge_guide 现拿现读。
手册过期 = AI 按旧规则写出过不了校验的插件包(校验拒装只是兜底,用户体验是“AI
反复打包反复被拒”)。
ghost_forge_pack 的边界严格止于校验和生成 .cindy 产物;打包成功不构成安装依据,
不得顺带安装、更新或启用插件。安装由用户导入本地包、明确要求当前 Agent 调用独立的
ghost_forge_install、点击市场安装或服务端 defaultInstall 明确触发;Forge 安装与本地
导入必须复用同一套 Main 安装/更新事务。
凡改动插件作者可见的契约,同一改动内必须同步更新手册对应章节:
- (a)
ghost.json身份卡字段或校验规则(shared/ghost.ts的validateGhostManifest); - (b) 管子协议(
cindy.send/cindy.onHostMessage的消息形态,shared/ghost.ts管子类型); - (c) Cindy 模型代办能力的 kind/参数/模型白名单;
- (d) 面板供片协议与注入的主题 token(
cindy-ghost://分支、ghostPanelTheme.ts白名单); - (e) 打包限制(
forge.ts的packGhostDir)。 - (f) 安装、来源绑定、自动更新与能力展示的用户可见行为。
其中 ghost.json 属于 Ghost manifest 协议。新增或修改字段、v2 兼容映射或枚举时,至少同步:
- 协议正本
packages/plugin-protocol/src/manifest.ts及其测试; - Desktop 完整镜像
apps/desktop/src/shared/ghost.ts及其测试; - 作者文档
FORGE_GUIDE的对应章节及 Forge 测试。
mainView 的现行协议在 FORGE_GUIDE §4.20;v2 的 main-view 只保留输入兼容。
mainView.icon 只接受 Cindy 系统线性图标
puzzle、globe、code、folder、database、chart-column、image、
message-circle、calendar-days,缺省回退 puzzle。枚举值直接等于图标名,不设别名;
该字段只控制主视图侧边栏入口,不替代或修改根级 icon 品牌图片协议。后续扩展该枚举时也
必须遵守第 5 节的存量兼容红线,并核对第 7 节的旧客户端降级缺口。
反向同样成立:改校验必须同步手册;改手册宣称的新能力必须真有实现。forge.test.ts 的
关键章节存在性测试只是最低闸,不替代逐条人工核对。
PR 约束:命中上述任一路径的 PR,Description 必须写明「手册已同步(改了哪节)」或 「无需同步 + 为什么不涉及作者契约」;漏同步 = P1。
以下缺口在触及相关链路时必须一并修复,或在 PR 中保留明确的正式跟踪,不得静默丢弃:
- 存量安装的 receipt backfill 必须无感。
GhostManager.migrateLegacyApprovalsOnce()在对账前 从旧安装事实重建等价的安装状态记录;迁移必须崩溃安全、不扩张 manifest 能力、只写状态根, 并受 owner 租约保护。能自动重建时不得要求用户重新安装或确认能力;确实无法重建时应进入统一的 legacy recovery 状态并保留凭证与偏好,不能伪装成普通停用,也不能把能力审批弹窗当恢复方案。 - manifest 扩展的降级兼容(#1283)。 v3 通过“未知顶层字段原样保留,但当前 Host
不展示、不授权、不阻止安装”解决向前兼容;能力一旦被 Host 支持,必须成为明确的直接字段
和权限映射。v2 仅保留输入兼容:未知但格式合法的 slot 允许安装、运行时不提供能力。
不得恢复独立的 Host slot 支持表或按名字猜测映射。能力动作和订阅事件只校验标识格式,
不能以当前实现列表阻断声明;已知字段类型、安全路径、协议格式及运行时凭证边界仍校验。
schemaVersion > 3仍以GHOST_HOST_UNSUPPORTED拒绝,避免 当前 Host 误读整体结构变化。已经发布的旧 Host 无法追改,降到 v3 改造前仍可能拒绝新清单。 - 声明与实现进度解耦。 插件开发不应等待客户端先注册每一项扩展;声明被接收不等于
API 已存在或获授权。插件必须检查所需接口是否存在,处理不支持响应,保留可用功能并对
不可用部分给出升级提示。不得把权限拒绝、账号失效或网络故障当作“不支持”绕过。
minCindyVersion用于版本兼容声明与分发选择,不是运行时能力探测:手动导入、 旧版客户端或其它分发入口仍可能让不适配客户端安装插件,必须有运行时降级路径。 networkSlot.ts的as: 'media'不能只信任 Content-Type(GLB 常见application/octet-stream),需要安全的 magic-byte/扩展名嗅探。- SSH 远程场景必须让
LiziMcpSessionContext携带 remote 标识;目录过户不得回退读取本机 同名路径,无法证明来源时 fail closed。 - 手机版仍需把历史 mivo 动作按钮降级为纯展示。
- 安装内容字节仍可变、且加载时不校验。 批准 receipt 钉住的是授权事实
(manifest/trust/启停/revision),逻辑页代码仍从
cindy-brain/<id>/现读;packageSha256只是批准时点的来源指纹(市场/本地包 =.cindy文件哈希,随包种子 = 内容目录哈希),没有任何运行期校验消费它。因此能写这个目录的本机进程仍可替换 代码,只是被限制在此前已批准的权限集内运行,且不能借改写ghost.json扩权。 技能目录因为越出沙箱已单独拷成快照(第 3 节),其余内容的持续完整性校验仍未做—— 改动装入链路时不得声称已有内容完整性保证。 - 批准状态根自身没有写保护。 这是与上一条并列、但不同的缺口:上一条说的是
内容根字节可变,这一条说的是
<userData>/ghost-install-state/本身对同权限本机进程 可写。当前实现已经把能在写入侧关掉的窗口关掉了,剩下的是消费侧的窗口,两者要分清:- 已关闭(写入侧):技能快照的字节指纹在每次写批准事实时都重新核对——接受既有
快照前核一次、复制到临时目录后核一次、
rename就位后再核一次(skillSnapshotMatchesReceipt是唯一判据)。因此"复制完到 rename 之间被改写"与"快照事后被就地改写"都会在下一次 写批准事实时暴露:对不上就删掉重建,重建仍要过安装目录的字节校验,安装字节也漂移 时一律拒绝并要求重新安装合法包。 - 仍未关闭(消费侧瞬时窗口):启动/装卸/启停广播触发技能对账时,Host 会在建立
或保留私有投影前重新核对整棵批准快照,加载清单也核验批准来源;但核对之后、主 Agent
读取技能之前,快照仍可被同权限进程改写。Agent 的读取路径不在宿主控制内,宿主不做逐次
校验。receipt 同理——它有严格结构与字段校验(改坏即判
invalid、fail closed), 但没有签名或 MAC,能写状态根的进程可以伪造一份结构合法的批准。 - 彻底关闭需要给状态根加签名/MAC 或 OS 级写保护,尚未做;改动批准链路时不得声称 批准状态不可伪造,也不得把"写入侧已核对"说成"消费时读到的一定是被批准的字节"。
- 已关闭(写入侧):技能快照的字节指纹在每次写批准事实时都重新核对——接受既有
快照前核一次、复制到临时目录后核一次、
- 点开头目录的内容不进随包指纹(有意为之,不是缺口)。
fingerprintDirContent与hashApprovedDirectory都跳过.开头条目(.disabled、.cindy-trust.json是用户与 宿主状态,不是插件内容),点开头目录整条不递归。之所以安全:清单里所有相对路径 (entry/node.entry/panel.html/settingsHtml/icon/locales/ skilldir)都过isSafeGhostRelativePath,段首字符必须是[a-zA-Z0-9_],任何声明都不可能 指向点开头目录里的文件——它们既不会被当代码加载,也不会被当技能读取。点开头条目本身 的类型仍然要判(名为.x的链接会翻起hasNonRegularEntry并触发重新播种), 判定顺序见ghostContentTree.collectGhostContentFiles。改这条策略前先确认清单路径 正则没放开首字符。
移动页面契约见 移动插件接入。可选 mobile 声明
只选择已有能力的页面入口,不改变旧插件批准、凭证或安装布局。逻辑仍由执行电脑运行。
Host 为页面消息附加 mobilePageId,原生弹窗、任务操作、目录选择和预览等必须保留
该来源;页面覆盖、关闭、账号/连接/安装代次改变后,旧响应不得继续写入或清除未读。
手机任务配置复用普通任务配置校验。密钥/连接表单走 Host 原生 v3 加密授权通道,
不得通过 WebView、BroadcastChannel 或通用插件 fetch 传递凭证。
插件能力可能运行在 SSH 远程工作区、设备互联远程控制或手机版控制端。新增或修改 IPC
channel 与推送事件时,若手机/远程控制场景需要用到,必须按
packages/device-link/src/allowlist.ts 顶部注释的准入判据登记 invoke/push 白名单并同步
topic 路由;产品层多端语义见
../product-rules/core-product-principles.md 的
「多端连接与任务连续性」。缺登记会让手机/远程控制端永远调不通,且静态检查发现不了。
- 沙箱是否保持进程隔离、专属 partition、无 Node/宿主 FS/通用网络直连?HTTPS 图片
例外是否仍严格限定为
protocol === "https:" && resourceType === "image"?身份是否由 主机反查而非信任 sender 自报? - 是否先按执行者分清边界:当前 Agent 在途的通用操作是否严格绑定同插件、同会话、
未交卷的
callId并复用 Agent 授权;插件自主 Host 能力是否以 manifest 直接字段声明、 在详情如实展示并由 Host 守门?是否误把安装弹窗或前端展示当成授权事实? - receipt 是否仍只由 Main 的合法安装/更新/迁移事务写入?跨进程更新是否回传
ghostInstallApprovalToken()并在锁内重读比对?缺失或损坏状态是否优先无感迁移, 而不是用安装确认充当恢复方案?停用方向是否始终可成功?技能快照与字节指纹是否仍受保护? 3.5. 新增或改动的装入路径是否显式交出安装确认策略,并在锁内用真实包复核 (assertGhostInstallConsent)?确认等待是否在所有安装锁与 owner 租约之外?后台路径是否 只会automatic放弃、从不替用户确认?Agent 路径是否不受任务权限档影响、始终确认? - Agent 在途网络是否仍通过 URL/SSRF/重定向等 Host 守门,自主网络是否限 manifest 白名单,托管凭证是否只向声明且命中的 host 注入、无明文读回?附件/媒体/目录是否经 归属校验的 grant/deposit/ledger 交接,未暴露宿主绝对路径?
- 内联凭证是否只走 trusted Desktop 专用 IPC,未登记 device-link?Main 是否重新校验 sender、request、revision、action、精确字段集合与 manifest 绑定?
- Forge(scaffold/pack/install)是否排除了 Host 受管根,按 realpath 双向判定并挡住
大小写与软链/junction 别名?
ghost_forge_pack是否仍然只产出.cindy,没有隐式安装;ghost_forge_install是否只在用户明确要求时调用并复用本地安装/更新事务? 6.5. 新增的“读插件内容目录”代码是否走ghostContentTree.ts的统一分类、逐段路径解析和 指纹格式,而不是就地复制一份readdir/stat实现? 6.6. 存量插件升级后还能不能用(第 5 节红线):改动安装状态记录、manifest、能力字段、 快照、安装根、包格式或管子协议时,是否提供无感 backfill,保留已装、启停、凭证与偏好, 并覆盖旧布局 fixture?让存量插件失效且无迁移 = P0。 - 改动是否命中作者可见契约(身份卡/管子/模型代办能力/面板供片/打包)?命中就必须
同步
FORGE_GUIDE并在 PR 说明;漏同步 = P1。 - 第 7 节的已知缺口是否被触及?触及是否一并修复或留了正式跟踪?
- 新增 IPC/推送是否需要远程/手机版?需要就登记 device-link 白名单与 topic 路由。
最小验证入口:
pnpm --filter desktop exec vitest run src/main/cindy-brain
pnpm --filter desktop typecheck其余按 desktop-development.md 的分层验证选择;命中媒体、协议或
IPC 时追加对应专项规则要求的验证。
插件自己的同源 GET /agent-models 面向 settings、panel、mainView 与电子脑。
无需新增 manifest 权限,不改变存量批准状态。仅投影已连接来源中可用于新任务的本机
id/name/agent/providerId/providerName/efforts/defaultEffort/visible,来源级强度不能被跨来源合并。
visible 复用当前账号模型选择器的用户开关与目录默认值;隐藏项保留供显式展开,偏好未就绪不伪造默认。
使用 ProviderService 的 allowSideEffects:false 读取,不主动发现或认领凭证,不调用模型。
不暴露 ProviderView、auth、账号 identity 或 endpoint;响应 no-store、仅 GET、无参数,
检查插件在装启用与 owner 归属,异步返回前复核。旧 Host 404 时插件应明确提示升级。
此端点不覆盖远程 SSH 模型,执行前仍需核验真实路由。