☰
Kun 房间审批机制与私聊权限模式:从确认卡片到沙箱确认窗口的完整安全链路
2026/10/10 5:24:32 网站建设 项目流程
  • 人工智能
  • AI Agent
  • 自主智能体
  • 桌面应用
  • MCP Clients

【免费下载链接】Kun

Local-first AI agent workspace for coding, writing, design, research, and automation — one runtime for desktop GUI and TUI.

项目地址:https://gitcode.com/gh_mirrors/de/Kun
点击查看免费下载

Kun 的 Rooms(房间)功能为私聊 Agent 会话提供了一套完整的工具审批与权限控制体系:用户在私聊输入框中看到紧凑的审批卡片,点击后进入独立的沙箱化 Electron 确认窗口,而会话的三种权限模式(Ask for approval / Approve for me / Full access)则决定工具调用是否需要人工批准、由谁审查以及沙箱边界。本文以 docs/rooms-approvals.md 为核心骨架,结合kun/src下的路由、契约与审批门实现,以及scripts中的桌面端冒烟场景,逐层拆解这条从"卡片展示 → 沙箱确认 → 权限落库"的安全链路。

一、审批卡片:紧凑、可读、多语言适配的展示层

在私聊会话中,Agent 请求执行某个工具(如写文件、跑命令、调 MCP)时,Rooms 会渲染一张紧凑的审批卡片,卡片上呈现三类核心信息:

  • 待处理的动作(pending action):Agent 想要执行什么;
  • 工具(tool):调用的是哪个工具(如文件写入、命令执行);
  • 工作目录(working directory):动作将作用于哪个目录。

卡片上的命令与文件内容保持完整可读,当内容过长时,长文本在卡片内部滚动而不是截断或溢出。从源码看,审批请求的数据结构由 kun/src/domain/approval.ts 中的ApprovalRequest定义,包含id、threadId、turnId、toolName、summary以及可选的action动作封套;status取值pending | allowed | denied | expired,createApprovalRequest以pending初始状态创建记录。这张卡片就是pending状态在 UI 上的可视化呈现。

卡片在布局上参考了中性风格的确认卡片设计,但需要强调的是:Kun 并没有照搬外部产品的工具审批协议,审批协议本身是本仓库独立实现的(见下文审批门与确认边界)。界面语言与主题层面,卡片支持英文、中文、浅色、深色以及窄窗口布局——冒烟场景中专门验证了中文深色主题与 760px 窄窗口下的渲染(scripts/smoke-room-approvals.cjs)。

二、确认边界:沙箱化的 Electron 确认窗口

审批卡片只是入口,真正的"放行/拒绝"决策发生在一个独立的沙箱化 Electron 窗口中。这是整条链路中安全设计最重的部分:

  1. 内容来源唯一化:用户点击"Review and allow"或"Deny"后,主进程(Main)从 Runtime 获取待处理的动作(pending action),确认窗口展示的是这份权威内容(canonical content),而不是渲染进程传来的任意文本。
  2. 窗口隔离:受保护窗口具有独立的会话(isolated session)、极简的 preload、没有 workbench 桥接、不加载任何扩展脚本。
  3. 以数据方式渲染动作文本:动作文本只作为数据渲染,不会被当作可执行内容处理;窗口只接受来自自身主框架(main frame)的可信按钮激活。

从实现看,主进程侧对"确认"的权威读取接口是GET /v1/approvals/:id,该路由在 kun/src/server/routes/register-thread-routes.ts 中注册,从runtime.approvalGate.get(id)读取审批记录并附带线程标题返回;而它不在通用渲染进程路径的允许清单(allowlist)上——也就是说,常规渲染进程的 HTTP 请求无法直接读到审批详情。

冒烟场景 scripts/smoke-room-approvals.cjs 对这条边界做了非常具体的验证:

  • 确认窗口内window.kunGui不存在(assert(!await consent.evaluate(() => Boolean(window.kunGui)))),证明 workbench 桥接被移除;
  • 确认窗口内存在window.kunProtectedRoom?.confirm函数,但脚本化地直接调用document.getElementById('confirm').click()无法确认审批(等待 120ms 后审批仍为 pending);
  • 只有点击"Allow once"按钮并等待窗口关闭,审批才真正放行。

这印证了文档中"只接受可信按钮激活"的设计:窗口内容不可被脚本伪造,决策必须经由真实用户交互。

审批令牌:短生命周期、单次使用、绑定精确动作

短生命周期的审批令牌(approval token)将精确的动作与决策绑定在一起。实现位于 kun/src/server/approval-consent.ts:

  • 令牌格式为v1.<expiresAt>.<nonce>.<signature>,签名由HMAC-SHA256基于runtimeToken生成;
  • 生命周期上限MAX_APPROVAL_CONSENT_LIFETIME_MS = 60_000(60 秒);
  • 单次使用:ApprovalConsentVerifier内部维护已用令牌摘要表,同一令牌重复提交直接返回失败;表满时拒绝而非驱逐未过期条目(fail closed,防止高并发下旧令牌可重放);
  • 签名校验使用timingSafeEqual,防止时序侧信道。

其关键行为语义为:

  • 关闭或取消确认窗口→ 待处理的审批保持原样(pending 不变);
  • 动作已过期→ 确认窗口关闭,审批视为失效;
  • 主进程拒绝渲染进程伪造"自动策略放行"的请求→ 自动审查(automatic review)只能由 Runtime 持有,渲染进程不可冒充;
  • 原有的手动确认展示(manual confirmation presentation)代码保持不变。

对应地,POST /v1/approvals/:id路由(register-thread-routes.ts)在决定审批时要求提供x-kun-approval-consent头中的同意令牌,未通过校验则返回 403(见 kun/src/server/routes/approvals.ts 中decideApproval的ERRORS.forbidden('protected approval consent required'))。底层审批门接口定义在 kun/src/ports/approval-gate.ts,其reserveDecision / commitDecision / rollbackDecision语义确保:先持久化approval_resolved审计事件,再放行循环执行被允许的工具;审计持久化失败则回滚决策,审批回到 pending。

三、Composer 权限模式:三种模式与底层策略轴的映射

私聊 composer 复用 Code 的权限选择器(permission picker)与预设定义。三种模式与底层策略轴的对应关系如下:

模式审批策略 (approvalPolicy)沙箱模式 (sandboxMode)审查者 (approvalReviewer)
Ask for approval(请求批准)on-requestworkspace-writeuser(用户)
Approve for me(替我批准)on-requestworkspace-writeagent(Agent 自动审查)
Full access(完全访问)autodanger-full-accessuser(用户)

这一映射由 kun/src/contracts/policy.ts 中的kunToolPermissionModeSettings精确实现,三种模式恰好对应三组完整快照(KUN_TOOL_PERMISSION_MODES = ['ask-for-approval', 'approve-for-me', 'full-access'])。反向投影函数kunToolPermissionModeFromSettings则把任意合法的权威快照投影回三种模式词汇表:自定义/旧版组合一律投影到受审的用户模式(除非精确匹配某种规范模式),确保"仅渲染选择器"永远不会把旧数据呈现为不受限状态;调用方不得把投影结果持久化,除非用户显式选择了该模式。

从 kun/src/contracts/policy.ts 还可以看到,策略轴(approval policy / sandbox mode / reviewer)与产品模式是分离的:APPROVAL_POLICIES包含always / on-request / untrusted / never / auto / suggest六个原始值,SANDBOX_MODES包含read-only / workspace-write / danger-full-access / external-sandbox,APPROVAL_REVIEWERS为user / agent——每个客户端写入的是同一份完整的权威快照,同时不压缩原始兼容契约。

模式选择的会话归属

  • 选择属于当前私聊会话:不会改变全局 Code 设置或其他会话;
  • 未配置的会话默认从Ask for approval开始;
  • 切换模式需要受保护的确认(即上一节的沙箱确认窗口),随后通过 Manager store 持久化,带修订号校验(revision checks)与幂等的请求标识(idempotent request identity),并发布既有的 room update 事件;
  • 读取设置永远不会调用模型。

从 kun/src/agents/agent-permissions.ts 的实现看,setAgentPermissions以room-permission:<roomId>:<clientRequestId>为幂等键,用roomFingerprint(input)校验请求是否中途被篡改(指纹不一致抛RoomStoreConflictError('permission request changed'));提交时通过checks校验期望修订号expectedRevision,然后以room.updated事件广播变更,并将privateExecutionPolicy写入房间记录、revision自增。

四、已接受/排队请求的冻结策略与重试语义

权限模式切换对正在进行的请求有严格的语义划分:

  • 已接受(accepted)与排队(queued)的请求保持其冻结的策略(frozen policy)——它们不受模式切换影响;
  • 新请求使用新的选择;
  • 重试(retry)被视为一次新尝试:使用当前已确认的策略与 Agent 限额,但保留原始工作区;
  • 未知执行(unknown executions)必须先被协调(reconciled)才能重试;
  • 切换模式会重建不兼容的内部线程(threads),重建时从当前上下文纪元(context epoch)携带有界的引用历史(bounded reference history)。

这份"冻结策略 + 重试按当前策略执行"的设计,保证了模式切换过程中不会出现既有请求在新旧策略之间漂移的安全歧义。文档记录的验证覆盖了冻结权限、当前 Agent 上限(ceilings)、外部写入以及无效/重放的同意令牌等场景。

五、Full access 模式:边界与保留的限制

Full access 会移除私聊会话默认的仅工作区(workspace-only)工具作用域,但以下限制仍然强制生效:

  • Agent 只读预设(read-only presets);
  • 目录上限(directory ceilings);
  • 显式的工具、MCP 与技能(skill)限制。

关键约束是:带目录上限的 Agent 不能选择 Full access;选择后若限额发生变化,会在准入(admission)时重新检查。实现中,kun/src/agents/agent-permissions.ts 的agentPermissions会计算fullAccessUnavailable:当 Agent 预设toolPolicy === 'readOnly'时返回read_only_agent,当allowedRepositoryRoots已定义(即存在目录上限)时返回agent_directory_limits;setAgentPermissions在请求full-access且fullAccessUnavailable存在时直接抛RoomStoreConflictError拒绝切换。此时 GET 端点返回的策略会降级为 ask-for-approval,UI 选择器上的 Full access 选项不可用。

此外:

  • 群组讨论(group discussions)对写操作与命令保持只读,但文件读取工具可以检查用户点名的本地路径;
  • 遗留任务执行(legacy task execution)仍被限制在**授权的任务检出目录(authorized task checkout)**内;
  • 私聊选择器在**遗留任务草稿(legacy task drafts)**中隐藏。

六、权限读取与变更的 HTTP 契约

读取:GET /v1/rooms/:roomId/direct/permissions

返回当前房间的权限快照。路由实现在 kun/src/server/routes/register-room-permission-routes.ts,返回结构为:

{ "roomId": "...", "revision": 0, "policy": { "approvalPolicy": "on-request", "sandboxMode": "workspace-write", "approvalReviewer": "user" }, "mode": "ask-for-approval", "fullAccessUnavailable": null }

mode由kunToolPermissionModeFromSettings(policy)投影得出;fullAccessUnavailable取值read_only_agent、agent_directory_limits或undefined。注意该端点要求会话类型必须是user_agent(私聊),否则抛出Private conversation required。

变更:PUT /v1/rooms/:roomId/direct/permissions

变更不能通过通用渲染进程 HTTP 请求完成——文档明确"Generic renderer HTTP requests cannot PUT this setting"。它必须通过:

  1. 专用的受保护 preload 方法发起;
  2. 携带签名的一次性同意令牌(signed, one-use consent),令牌绑定roomId + clientRequestId + expectedRevision + mode四元组(consent subject 实现在 kun/src/contracts/room-permissions.ts 的roomPermissionConsentSubject,格式为room-permission-v1:<JSON>);
  3. 请求体符合 kun/src/contracts/room-permissions.ts 的RoomPermissionRequestSchema:clientRequestId(1–128 字符)、expectedRevision(非负整数)、mode(三模式枚举),且为严格对象(.strict())。

服务端在 register-room-permission-routes.ts 中先解析请求体,再用ApprovalConsentVerifier.verifyAndConsume校验x-kun-approval-consent头,失败返回403 Protected permission confirmation required,成功后才在房间独占锁(rooms.exclusive)内执行setAgentPermissions。

七、验证矩阵:回归测试与真实桌面端冒烟

文档记录了完整的验证结果:

  • Runtime 回归:67 个套件 / 437 个测试通过;最终的准入与重试改动额外通过 14 个定向测试;覆盖冻结权限、当前 Agent 上限、外部写入、无效/重放同意令牌;
  • Renderer/Main 回归:28 个套件 / 152 个测试通过;最终受保护对话框与发送方检查通过 32 个定向测试;
  • 类型检查、完整构建(含 build:kun)、lint 与 700 行门禁(gate)通过;lint 报告 30 个既有警告、0 错误;
  • 真实 Electron + Manager + Kun 队列验收:使用隔离数据空间与离线模型夹具(offline model fixture),不调用真实模型即可完成权限/UI 验收,覆盖:UI 创建与发送、手动 allow 与 deny、拒绝合成的脚本激活、伪造的策略放行(forged policy allow)、自动审查、真实 full-access 外部文件创建、取消变更、全局设置保持不变、持久化、中文深色模式与 760px 窗口。

运行桌面验收场景

构建完成后,运行以下命令(文档原样给出的命令):

KUN_APPROVAL_EVIDENCE=/absolute/evidence/path \ node scripts/smoke-development-direct-chat.cjs --approvals \ --evidence /absolute/evidence/path

要点:

  • --approvals开关让场景进入房间审批专项流程(scripts/smoke-development-direct-chat.cjs 会加载 scripts/smoke-room-approvals.cjs 的exerciseRoomApprovals);
  • 证据目录(evidence)必须放在一次性工作树(disposable worktrees)之外,避免被清理;
  • 场景会输出一份 JSON 结果报告(report.json)与多张截图:内联审批卡片(approval-inline-card)、受保护确认窗口(kun-protected-approval.png)、三种权限模式(kun-permission-*.png)、中文深色模式审批(kun-protected-approval-zh-dark.png)、窄窗口 composer(approval-narrow-composer)等。

冒烟场景断言的价值在于它同时覆盖了安全边界与功能正确性:window.kunGui.resolveKunApproval伪造策略放行必须抛错、脚本化点击confirm不得生效、deny 后目标文件必须不存在、房间内切换到 full-access 后外部目录文件确实能被创建、全局权限设置前后必须一致(assert.deepEqual(await globalPolicy(), before)),以及刷新页面后权限模式必须持久化。

八、总结

Kun 的房间审批体系是一条"展示层 → 确认层 → 持久化层"层层收紧的信任链:内联卡片只负责可读呈现;真正的决策发生在无桥接、无扩展、仅接受主框架按钮激活的沙箱窗口;每次变更都必须携带短生命周期、单次使用、绑定精确语义的 HMAC 同意令牌,并经修订号校验与幂等键落库到 Manager store;而三种权限模式只是同一套底层策略轴(审批策略、沙箱模式、审查者)上的产品化预设。理解这条链路,就能在私聊 Agent 场景中安全地配置"谁可以批准、以多大沙箱权限执行、如何回滚与重试",同时确保权限变更既不污染全局设置,也不会被渲染进程或脚本冒充。

  • 人工智能
  • AI Agent
  • 自主智能体
  • 桌面应用
  • MCP Clients

【免费下载链接】Kun

Local-first AI agent workspace for coding, writing, design, research, and automation — one runtime for desktop GUI and TUI.

项目地址:https://gitcode.com/gh_mirrors/de/Kun
点击查看免费下载

相关推荐

上一篇:compact_str vs String:基准测试揭示惊人性能差异(附完整代码)
下一篇:10倍提升标注效率:LabelImg预定义类别与智能补全实战指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询