Skip to content

维护者确认: #1719 feat(ghost): 按 endpoint 收窄凭证注入 #1948

Open

Description

@MagicLizi

PR #1719 触发了维护者确认门。

触发类型: product
触发原因: feat 类型,新增插件凭证注入收窄策略
作者: FIERsity

请维护者审视此 PR 的产品方向,确认后在 PR 上 Approve;需要修改请 Request Changes,作者改完后重新 Approve 即可放行。


关联 PR:#1719(作者 @FIERsity);本 issue 由 review-pr 流程自动创建,用于先讨论该 PR 涉及的产品 / UI 变更,维护者确认后 PR 会恢复推进。

Activity

  1. dash-s-cindy commented on Aug 6, 2026

    @dash-s-cindy

    判断

    同一域名下的公开端点与鉴权端点需要不同的凭证注入范围,这是 Ghost network 能力中真实存在的表达缺口。为 network.secrets[].inject 增加精确 pathname 与 HTTP method 白名单,可以让公开接口在未配置 Key 时正常访问,也能减少凭证被发送到不需要它的同域端点;这个产品方向值得继续推进。

    当前 file-converter 场景已有插件侧临时规避方式:删除未使用的 Secret/整域注入声明后,公开 /mcp 可以恢复工作。但当插件未来必须同时调用同域的公开 /mcp 与鉴权 /v1/convert 时,现有 manifest 仍没有安全、准确的表达方式,因此临时规避不能替代宿主能力。

    行动

    建议维护者确认产品方向,但暂时不要直接 Approve #1719。当前 PR 已经从 Draft 转为 Ready;直接 Approve 会解除合并门,而 mixed-version 的 fail-closed 发布机制仍未落地。更合适的处理是先 Request Changes,要求在放行前完成以下二选一方案,并以第一种为推荐:

    1. 推荐:为 endpoint-scoped secret 增加旧客户端必然拒绝的 capability/schema 标记。 只要 manifest 使用 inject.paths 或 inject.methods,就必须同时声明该标记。支持新版可正常安装;不认识该标记的旧版应整包拒绝,而不是忽略收窄字段后继续按整域注入。
    2. 备选:实现可强制执行的最低客户端版本门。 该门必须同时覆盖 SkillHub/市场分发和本地 .cindy 安装;只在服务端隐藏新版插件不足以保护手动安装包。当前源码快照中未发现 manifest 或安装链路可执行的 minAppVersion/minimumAppVersion 机制,因此不能把它当作现成保障。

    安全发布顺序应为:先落地 fail-closed capability/version gate 与 Desktop 支持并发布,再合入协议定义及服务端消费,最后才允许发布实际使用新字段的插件。若团队希望先合并纯宿主实现,也应把协议公开、作者手册对外宣告和插件发布继续锁在同一发布门后,不能把“暂不发布”作为唯一安全边界。

    已确认的现状

    基于 origin/main@3cce98e788c8fabfb913d3b27a1152c55094adfe:

    • apps/desktop/src/shared/ghost.ts 的 GhostSecretInjectDecl 当前只有 hosts、header、format,没有 path 或 method 条件。
    • validateGhostManifest() 只校验 inject.hosts 是 network.hosts 的子集。由于 manifest 在规范化时只重建已知字段,旧客户端会接受包含未知 paths/methods 的原始对象,但在规范化结果中丢弃它们。
    • apps/desktop/src/main/cindy-brain/networkSlot.ts 的 injectSecrets() 仅以 hostname 判断是否注入。命中 hostname 但凭证未配置时,请求会在真正出网前快速失败,这与 [Ghost] 同域公开与鉴权端点无法分别控制凭证注入 #1714 中公开 /mcp 被可选 Key 阻断的表现一致。
    • 当前运行时会在每次重定向时重新调用 injectSecrets(),但重新判断的仍只有 hostname;因此同域从受保护 path 跳到公开 path 时,现有版本不会撤掉该域对应的凭证。
    • 仓库规则明确要求网络凭证边界由 Host 强制,并要求旧版读到新状态时不能把它误解释为更宽权限。这里的风险不是理论上的跨域泄漏,而是旧客户端把作者声明的 endpoint 范围静默扩大为现有 host-only 范围。

    对 #1719 实现的判断

    #1719 的运行时方案整体已经收敛到合理的第一版:

    • paths 使用精确 URL.pathname 匹配,query 不参与;
    • methods 显式声明时才限制,二者与 host 为 AND 关系;
    • 初始请求、每一跳重定向以及 401 重试都按实际 URL 和实际 method 重新匹配;
    • endpoint 未命中时仍清除插件伪造或上一跳残留的宿主管理凭证头;
    • 未声明新字段的旧 host-only manifest 保持现有行为;
    • endpoint-scoped 权限详情采用稳定排序,同时保留旧 host-only manifest 的既有规范化结果和摘要;
    • FORGE_GUIDE 与中英日韩权限文案已同步。

    PR 描述报告的验证包括 Desktop typecheck、完整 unit tier、ghost.test.ts/networkSlot.test.ts/forge.test.ts 共 284 个定向测试,以及 i18n 与相关检查。PR patch 中也能看到精确 path/method、query、同域重定向、302 POST→GET 后按实际 method 重算、伪造凭证头剥除等覆盖。就匹配逻辑本身看,没有发现需要改变产品方向的反证。

    真正尚未闭合的是发布兼容:旧 schema-v2 客户端会忽略新字段并继续使用 host-only 语义。这虽然“不比旧产品现状更宽”,却会违反新版插件作者和用户所依赖的安全声明——作者以为凭证只会发往 /v1/convert,旧客户端实际上会把它发往同域所有路径。对于凭证授权边界,这种静默退化不能作为可接受降级。

    建议补充的验收项

    在现有运行时测试之外,合并门应增加 mixed-version 验收:

    1. 使用 paths/methods 的 manifest 在支持版客户端上正常安装,并保持字段穿过安装、市场 release manifest 规范化和运行时读取链路。
    2. 同一 manifest 在不支持 endpoint scope 的旧客户端上必须明确拒装或被最低版本门挡住,不能安装后退化为 host-only。
    3. 上述拒绝必须同时覆盖市场安装、更新和本地 .cindy 安装。
    4. 未使用新字段的存量 host-only manifest 在新版中的规范化结果、权限 baseline、摘要、批准状态和已保存凭证保持不变。
    5. 从私有 path 重定向到公开 path 时移除凭证;反向跳转仅在目标 endpoint 完整命中时注入。
    6. 401 重试只刷新最终响应实际使用过的凭证,且不得因重试扩大 endpoint 范围或重放不允许自动重放的副作用请求。
    7. 对路径编码、反斜杠、..、尾斜杠、大小写和重复项保持固定的 fail-closed 测试。

    相关记录

  2. FIERsity commented on Aug 8, 2026

    @FIERsity
    Contributor

    mixed-version fail-closed 门禁已实现(方案 1:capability/schema 标记)

    按 bot 推荐方案 1,为 endpoint-scoped secret 实现了旧客户端必然拒绝的 schema 标记:schemaVersion 3。已同时推送到 #1719(桌面)与协议仓 #30(HEAD 6d1a4f2)。

    机制

    • inject.paths / inject.methods 仅在 schemaVersion 3 清单中合法;v2 清单声明这两个字段 → 校验直接拒装(报错指明必须升级到 3)。
    • 旧客户端对 schemaVersion 的检查是严格相等(raw.schemaVersion !== 2 → 整包拒绝,桌面 ghost.ts 与协议仓 manifest.ts 均如此),因此声明了新字段的 v3 清单在旧客户端上必然拒装,不会被静默忽略收窄字段后退化为整域注入。
    • normalize 输出保留输入版本(v3 清单不再被降回 v2),manifestDigest 与打包时一致。
    • 存量 v2 host-only 清单:校验、规范化结果、排序与 digest 全部保持逐字节不变;v3 清单也允许纯 host-only 声明(向后兼容)。

    逐条对照验收项

    1. ✅ paths/methods 清单在支持版上正常安装,字段穿过校验、市场 release manifest 规范化(delivery 测试)与运行时链路
    2. ✅ 同一清单在旧客户端(schemaVersion 严格相等检查)必然整包拒装,不会退化为 host-only
    3. ✅ 拒装发生在 validateGhostManifest 统一入口,覆盖市场安装、更新与本地 .cindy 安装
    4. ✅ 存量 host-only 清单规范化结果、权限 baseline、digest 不变(新增回归测试:hosts 保持声明顺序)
    5. ✅ 重定向按实际 host/pathname/method 重新匹配,命中公共 path 时清除凭证(已有测试)
    6. ✅ 401 重试只按最终响应实际使用的凭证刷新,method 降级后不重放副作用请求(302 POST→GET 测试)
    7. ✅ 路径编码/反斜杠/../尾斜杠/大小写/重复项 fail-closed 测试(已有)

    新增测试:协议仓 3 个(拒绝 v2+paths/methods 三种形态、v3 host-only 放行、v2 拒装 reason),桌面 2 个(v2 拒装循环、v3 host-only 放行);两仓原有 endpoint 测试升级到 v3。桌面 178+133 测试、协议仓 274 测试全过,tsc/eslint/i18n 门禁干净。

    发布顺序

    符合 bot 建议的安全顺序:宿主实现 + 门禁先合并,协议与服务端消费随后,实际使用新字段的插件最后发布。#1719/#30 均未发布任何使用新字段的插件。

    请确认后解除 Request Changes,放行 #1719 合并。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions