- 人工智能
- 大模型
- AI 应用
- 交互助手
- 本地部署
【免费下载链接】cherry-studio
🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端
外部即时通讯(IM)消息驱动的 Agent 运行,与桌面端用户在渲染器(renderer)中发起的交互式运行存在本质差异:没有人在看屏幕、没有交互式审批界面、入站内容完全来自不受信任的远端。本文基于 Cherry Studio 仓库中的设计文档 channel-ingress-security.md 展开,结合 ChannelMessageHandler.ts、OutputSanitizer.ts、settingsBuilder.ts 等源码实现,系统梳理入站链路(ingress flow)、当前已具备的安全防线、四个待关闭的安全缺口(G0–G3)以及推荐的安全基线姿势。读完本文,你将掌握:渠道运行与桌面运行在信任边界上的差异、现有防御层的具体代码落点、每个缺口的攻击含义与修复方向,以及如何在渠道场景下配置 Agent 使其达到保守安全基线。
威胁模型:一个不受信任的远端发言者驱动一个能触碰工作区的 Agent
渠道入口安全要回答的核心问题是:当一条来自不受信任远端方的消息,经由绑定的渠道(Slack / Discord / Telegram / 飞书 / 微信 / QQ)进入系统,驱动一个可以调用工具、读写会话工作区的 Agent 时,如何保证运行安全?这条运行链路没有人类在渲染器侧监督,因此它不能依赖任何"人工确认"机制作为兜底。
设计文档明确给出了这条链路的完整拓扑:
adapter(webhook/socket)→ ChannelManager 注册 adapter.on('message', …) → ChannelMessageHandler.handleIncoming(按会话 1s 防抖 + 串行队列) → processIncoming 解析绑定的 session + agent → startAgentSessionRun({ sessionId, userParts, listeners })其中每一环在源码中都有确切的落点,可以一一对应验证:
- adapter 事件注册:
ChannelManager.ts中通过adapter.on('message', (msg) => …)建立入站消息监听(ChannelManager.ts),收到消息后调用channelMessageHandler.handleIncoming(adapter, msg)。 - 入站入口与串行化:
handleIncoming(ChannelMessageHandler.ts)以${agentId}:${channelId}:${conversationId}:${userId}为键做每会话防抖批处理:默认MESSAGE_BATCH_DELAY_MS = 1000(1 秒),单个发送者最多可延长到MESSAGE_BATCH_MAX_DELAY_MS = 16000(16 秒),避免微信等 IM 用户连发短消息时每条都触发一次 Agent 往返;随后通过enqueueBatch进入每会话串行队列(chatQueues),保证同一会话同一时刻只有一个流在运行,杜绝并发交错(ChannelMessageHandler.ts)。 - 会话与 Agent 解析:
processIncoming先解析该会话绑定的 Agent 与工作区,若命中孤儿会话(agentId === null)则拒绝运行;随后把入站图片/文件持久化到工作区,并将文本与附件路径拼接后交给collectStreamResponse(ChannelMessageHandler.ts)。 - 单次运行约束:最终调用
startAgentSessionRun({ sessionId, userParts, listeners, headless: true, requireIdle: { expectedAgentId } }),并通过requireIdle保证同一${agentId}:${channelId}:${conversationId}同时只有一个运行(ChannelMessageHandler.ts)。
设计文档中提到的
handleIncoming(:54、:111)与ChannelManager.ts:301为文档写作时的近似行号,对应现代码中防抖/队列逻辑与adapter.on('message')注册位置。
已就位的防御层:五道防线及其源码落点
设计文档将"已经存在"的防御整理为一张表。结合源码,每一层都能看到具体实现:
| 防御层 | 代码位置 | 实际作用 |
|---|---|---|
| 输出密钥脱敏 | OutputSanitizer.ts(sanitizeChannelOutput,在 ChannelMessageHandler.ts 附近被调用) | 在 Agent 输出离开渠道之前,将 PEM 私钥、AWS / GitHub / Anthropic / OpenAI 密钥、Bearer Token 等替换为[REDACTED] |
| 工作区隔离 | 会话workspace.path;附件持久化在${workspace}/.cherry-studio/channel-* | Agent 的 fs 访问范围被限定在会话工作区内,但其强度取决于 Agent 的工具策略:一个绑定了宽泛 Bash/Write 且未做每渠道收窄的 Agent,实际上不会被有效约束 |
| 渠道白名单 | 各平台 adapter 的allowed_chat_ids/allowed_channel_ids配置(见下文) | 来自未白名单会话/频道的入站消息被静默丢弃 |
| 每会话串行化 | ChannelMessageHandler.ts 的chatQueues | 同一会话同一时刻只有一个流,杜绝并发交错 |
| 写静默(write-quiesce)入口闸门 | ChannelMessageHandler.ts 的isWriteQuiesced检查 | 备份恢复期间暂停渠道摄入,缓冲批次立即冲刷,静默期到达的消息被丢弃并告警(与 JobManager / AiStreamManager 的编排契约) |
输出脱敏的具体匹配模式
sanitizeChannelOutput(OutputSanitizer.ts)首先剥离[cite:...]内部引用标记(保留代码块中的字面示例),随后依次执行 9 组正则模式替换:
- PEM 私钥:
-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY----- … -----END … PRIVATE KEY----- - AWS 访问密钥 ID:
AKIA+ 16 位大写字母数字 - AWS 秘密访问密钥:
aws_secret_access_key =之后的 40 位 base64 - Bearer Token:
Bearer+ 20 字符以上 - 通用 key=value 密钥:
api_key/password/token/client_secret等键名后 16 位以上值 - GitHub PAT:
ghp_前缀 36 位以上 - Anthropic 密钥:
sk-ant-前缀 - OpenAI 密钥:
sk-或sk-proj-前缀 - SSH 公钥内容:
AAAA开头的 100 位以上 base64(要求混合大小写与数字,避免误伤统一字符串)
每次命中都会记录logger.warn('Redacted sensitive content from channel output', { pattern: name }),并返回{ text, redacted }供上层判断是否发生了脱敏。
渠道白名单的跨平台实现
允许列表按平台命名略有差异,但语义一致:列表为空 = 不限制;列表非空 = 仅允许列表内的会话/频道:
- 飞书:
allowed_chat_ids(FeishuAdapter.ts),入站时if (this.allowedChatIds.length > 0 && !this.allowedChatIds.includes(message.chatId))静默丢弃; - Discord:
allowed_channel_ids(DiscordAdapter.ts),同时匹配原始频道 ID; - QQ:
allowed_chat_ids,支持群 ID 白名单(QqAdapter.ts),mention_only=false时仍会尊重白名单过滤; - Slack:
allowed_channel_ids,且会把白名单同步进notifyChatIds(见 SlackAdapter.test.ts 的测试); - Telegram / 微信:
allowed_chat_ids,微信 adapter 的测试明确覆盖了"白名单外用户被过滤"与"白名单内用户被放行"两种场景(WeChatAdapter.test.ts)。
此外,/whoami命令会直接向当前会话回复Current chat ID: …,并提示"把这个值加入allowed_chat_ids(Discord 为allowed_channel_ids)即可接收通知"——这是白名单配置的常用获取方式(ChannelMessageHandler.ts)。
信任边界总结
设计文档用一句话概括了当前边界:
入站文本原样传递;入站文件/图片不做内容检查(持久化到工作区,Agent 经 Read 工具读取,受工作区约束);出站做密钥脱敏;发送者身份未校验(见缺口 1)。
待关闭的缺口:D1 评审的实质工作
设计文档指出当前实现与 v1 保持平级(v1 也没有入站认证),以下四个缺口是渠道入口安全后续工作的主体,按 G0–G3 编号。
G0 — 入站内容没有来源边界
入站文本被刻意原样传递给 Agent:不携带发送者前缀、边界标记、注入警告、归一化或检测日志。这是产品层面的显式决策——提示词包装(prompt wrappers)被移除,Agent 收到的是用户消息的字面内容。
安全含义:任何渠道内容都应视为不受信任输入。配置渠道绑定 Agent 的工具与权限时,必须以"这条文本可能来自恶意攻击者、可能包含提示注入"为前提。
G1 — 授权是会话级而非发送者级
各 adapter 的闸门作用于会话/频道白名单,因此:白名单群聊中的任意成员都能触发 Agent 运行。
修复方向(设计文档建议):可选的每渠道发送者白名单(用户 ID 列表),在 adapter 中与会话检查并列执行;默认关闭(会话级检查仍是基线),群聊场景可选开启;拒绝时静默丢弃(与会话闸门行为一致)。注意:现有的消息批处理键已包含message.userId(ChannelMessageHandler.ts),这为发送者级过滤提供了现成的数据基础。
G2 — 无监督运行的工具有审批但没有应答方
渠道运行不绑定任何渲染器,因此审批的emit无绑定对象。在 settingsBuilder.ts 的canUseTool回调中可以看到完整的拒绝链:
- 会话无实时工具策略快照 →
'Tool policy not ready'(拒绝); - 工具需要交互式应答(如 AskUserQuestion)且
interactionState.userResponse === 'unavailable'→ 按无应答者拒绝; hasLiveTurnStream === false(无实时交互流,渠道运行即属此类)且非后台 Agent →'Approval requested outside a live interactive turn — denying'(拒绝);- 审批 emitter 未绑定(渠道运行下
peekToolApprovalEmitter返回空)→'Approval emitter not ready'(拒绝)。
净效果:需要审批的工具在渠道运行中直接失败,除非 Agent 被设置为bypassPermissions——而这恰恰是不安全的绕过方式。这是外部运行设计的关键漏洞(设计文档原文:"This is the key external-run design hole")。
设计文档给出了两条可选方案(按产品意图二选一并记录决策):
- 策略驱动、无交互卡片(推荐):渠道运行下,把每个工具解析为
allow/deny的非交互式策略(Agent 的permission_mode+ 每渠道工具允许/拒绝列表),永不"询问"。未列入白名单的需审批工具以清晰、模型可见的理由拒绝(如 "not permitted on this channel"),让 Agent 可以继续或解释,而不是挂起。 - 带外审批(Out-of-band):通过渠道本身(回复 approve/deny)或配套渲染器通知把审批呈现给人类。更重,仅在"渠道上交互式审批确实是硬需求"时才采用。
G3 — 缺少每渠道权限覆盖
v1 允许渠道覆盖 Agent 的permission_mode;v2 把配置迁移到 Agent 身上后丢弃了该能力。源码中的两处 TODO 佐证了这一状态:
- ChannelMessageHandler.ts:
TODO(channel-perm-override): channel-level permission_mode used to mutate session.configuration in-place; with config now living on agent, this override needs to flow as a per-dispatch option instead. Tracked separately. - ChannelMessageHandler.ts:
/new命令路径上的同类 TODO。
后果:渠道无法比其绑定的 Agent更严格(例如:为一个权限宽泛的 Agent 提供只读工具集)。
修复方向:每渠道permission_mode+ 工具允许/拒绝覆盖,作为每分派选项(per-dispatch option)线程化传入startAgentSessionRun→ Claude Code 的toolPolicySnapshot,叠加在 Agent 策略之上;渠道只能收窄(narrow),绝不能放宽(widen)。这也是 G2 策略驱动方案所读取的杠杆。
推荐的保守基线(在 G1–G3 落地之前)
设计文档明确:渠道是opt-in、高信任的特性。在缺口关闭前,应记录并遵循以下保守默认值:
- 只对可信工作区启用渠道——渠道不是默认开启的能力;
- 要求显式会话白名单——每个渠道必须配置
allowed_chat_ids/allowed_channel_ids; - 给渠道绑定的 Agent 配置只读工具集——避免宽泛 Bash/Write 工具在没有人工监督时被远程触发;
- 不要对渠道连接的 Agent 使用
bypassPermissions——这是当前唯一"让工具在渠道上跑起来"的办法,但也是最不安全的办法。
修复优先级:G2 是第一个要修复的缺口,因为当前唯一让工具在渠道上工作的答案(bypassPermissions)恰是最不安全的。
状态与边界说明
设计文档声明该方案"在本 PR 中未实现——与 v1 平级(v1 也没有入站认证)",作为后续工作跟踪,本文档即评审者(D1)要求的设计答复。因此,本文中所有"建议方向""方案选项"均属于设计意图而非已落地功能;所有"已就位防御"均有源码与测试佐证。在 G1–G3 落地前,渠道运行的安全强度完全取决于管理员是否遵循上述保守基线。
相关代码与文档入口:
- 设计文档:v2-refactor-temp/docs/ai/channel-ingress-security.md
- 入站处理核心:ChannelMessageHandler.ts
- 渠道管理:ChannelManager.ts
- 输出脱敏:OutputSanitizer.ts
- 工具审批回调:settingsBuilder.ts
- 各平台白名单实现:FeishuAdapter.ts、DiscordAdapter.ts、QqAdapter.ts、SlackAdapter.ts、TelegramAdapter.ts、WeChatAdapter.ts
- 人工智能
- 大模型
- AI 应用
- 交互助手
- 本地部署
【免费下载链接】cherry-studio
🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端
相关推荐
ruflo V3 Security Architect Agent:从威胁建模到 CVE 修复的 Secure-by-Default 安全架构设计
ruflo V3 Security Architect Agent:从威胁建模到 CVE 修复的 Secure by Default 安全架构设计 本篇技术指南
人工智能AI Agent多智能体Agent 编排Agent 记忆工具调用代码智能体MCP 服务AI 评测Agent Control Specification(ACS)威胁与安全模型:面向 Agent 系统的无状态确定性策略运行时防护指南
Agent Control Specification(ACS)威胁与安全模型:面向 Agent 系统的无状态确定性策略运行时防护指南 导读 本文基于 poli
人工智能AI AgentAI 安全治理策略引擎Agent 沙箱认证鉴权Hyperresearch 16个子代理名册全解:每个Agent的角色与模型分配
Hyperresearch 16个子代理名册全解:每个Agent的角色与模型分配 Hyperresearch 是一个 Agent 驱动的研究知识库(agent
人工智能深度研究AI AgentMCP 服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考