跳转到内容

工具调用与安全权限

工具调用与安全权限

学习目标

读完本章后,你将能够:

  • 理解 Cherry Studio 中 MCP 工具调用的 Electron IPC 传递链路
  • 描述 redactSensitive 函数的脱敏策略与递归处理流程
  • 掌握 toolPermissions Redux slice 的用户权限控制机制
  • 分析 MCP 工具输入输出的 Schema 验证与安全序列化
  • 识别 MCP OAuth 认证流程中的安全加固点(回调服务器、URL 消毒、IP 验证)
  • 应用 IPC 输入验证与超时控制的防御性编程实践

前置知识

  • Electron 主进程 / 渲染进程架构与 IPC 通信模型(contextBridgeipcMain.handle
  • MCP(Model Context Protocol)基础:工具注册、调用协议、通知机制
  • JSON Schema 用于输入 / 输出校验
  • Redux Toolkit 的 Slice 与状态管理
  • 通用知识:工具调用协议与执行模型(见 common/tool-use/01-overview),本章不再重复四层架构、并行/串行策略、审批模型等通用设计,仅聚焦 Cherry Studio 的具体实现。

项目实践

1. MCP 工具调用的 IPC 传递链路

Cherry Studio 基于 Electron 构建,MCP 工具的调用通过 IPC 从渲染进程(Renderer)传递到主进程(Main)。整体流程如下:

关键安全节点

环节防御措施位置
渲染进程 → IPCcontextBridge 隔离上下文,仅暴露白名单 APIpreload/
主进程接收ipcMain.handle 中验证所有输入参数main/services/MCPService.ts
发送前redactSensitive 脱敏敏感字段main/services/MCPService.ts
返回前safeSerialize 安全序列化,防止原型污染main/services/MCPService.ts
权限检查toolPermissions slice 校验用户授权renderer/store/toolPermissions.ts

2. 敏感信息脱敏:redactSensitive 函数

MCP 工具调用可能携带用户的 API Key、Token 等敏感信息。redactSensitive 在工具参数发送给 MCP Server 之前进行脱敏处理。

脱敏字段列表

以下字段名(不区分大小写变体)会被自动脱敏:

authorization, Authorization, apiKey, api_key, apikey, token, access_token

处理流程

伪代码

函数 redactSensitive(key, value):
如果 value 是字符串:
如果 key 在敏感字段列表中:
返回 "[REDACTED]"
如果 value 长度 > 300:
截断为 value[0:300]
追加 "…{原始长度 - 300} more"
返回截断后的字符串
返回 value
如果 value 是对象:
对每个 [k, v] in value:
value[k] = redactSensitive(k, v)
返回 value
如果 value 是数组:
对每个元素 i in value:
value[i] = redactSensitive(null, i)
返回 value
返回 value # 其他类型原样返回

设计要点

  • 字符串截断:防止过长的工具参数消耗过多 Token 或触发上下文窗口限制。截断后追加 …<N more> 让模型知道有内容被省略。
  • 递归处理:支持嵌套对象和数组,确保深层嵌套的敏感字段也能被脱敏。
  • 字段名匹配:同时覆盖驼峰命名(apiKey)、蛇形命名(api_key)和无分隔符(apikey)变体,减少遗漏。

3. 用户权限控制:toolPermissions Redux Slice

Cherry Studio 在渲染进程通过 Redux 管理用户对 MCP 工具调用的权限。toolPermissions slice 存储了每个工具的执行授权状态。

权限粒度

  • Per-Tool:每个 MCP 工具独立配置 alloweddenied
  • Per-Server:可以批量禁用某个 MCP Server 下的所有工具
  • 临时授权:用户可选择”本次允许”或”永久允许”

状态结构(伪代码)

toolPermissions state:
servers: Map<serverName, {
enabled: boolean,
tools: Map<toolName, {
allowed: boolean,
grantType: "always" | "once"
}>
}>

4. MCP 工具 Schema 验证

Cherry Studio 使用 JSON Schema 对 MCP 工具的输入和输出进行校验,确保数据格式符合预期。

类型定义(@types

类型 MCPTool:
name: string
description: string
inputSchema: MCPToolInputSchema # 输入参数校验
outputSchema: MCPToolOutputSchema # 输出结果校验(可选)

校验流程

McpError 处理:遵循 MCP SDK 标准错误码,如 INVALID_PARAMSINTERNAL_ERRORTOOL_NOT_FOUND 等,确保错误信息在客户端和服务端之间一致。

5. 安全序列化:safeSerialize

MCP 工具执行结果在返回给渲染进程之前,需要通过 safeSerialize 进行安全序列化,防止:

  • 原型污染:恶意对象通过 __proto__ 修改 JavaScript 原型链
  • 循环引用:工具返回包含循环引用的对象导致序列化崩溃
  • 特殊类型注入DateRegExpBigInt 等非 JSON 原生类型的意外行为
函数 safeSerialize(value):
visited = Set()
函数 walk(obj):
如果 obj 不是对象或不是 null:
返回 obj
如果 obj 在 visited 中:
返回 "[Circular]" # 检测循环引用
visited.add(obj)
如果 obj.__proto__ 不是 Object.prototype:
标记为可疑对象 # 原型污染检测
result = {}
对每个 [k, v] in obj:
如果 k 是 "__proto__" 或 "constructor" 或 "prototype":
跳过 # 防止原型污染键
result[k] = walk(v)
返回 result
返回 JSON.stringify(walk(value))

6. MCP OAuth 认证安全

Cherry Studio 支持 MCP Server 的 OAuth 2.0 认证流程,涉及两个核心组件:

CallBackServer(OAuth 回调服务器)

安全要点

  • 随机端口:每次启动回调服务器使用随机端口,降低端口冲突和劫持风险
  • 本地回环:仅监听 127.0.0.1,不绑定 0.0.0.0,防止外部网络访问
  • 一次性使用:获取到 authorization code 后立即关闭服务器

McpOAuthClientProvider

封装 OAuth 客户端逻辑,负责:

  • 存储和刷新 access_tokenrefresh_token
  • 执行 PKCE(Proof Key for Code Exchange)流程
  • Token 过期自动续期

7. URL 安全消毒与 IP 验证

当 MCP 工具涉及 URL 操作时,Cherry Studio 使用多层防护:

防护措施

层级工具防护目标
URL 格式strict-url-sanitise协议白名单(仅 http/https)、畸形 URL 拦截
IP 地址ipaddr.jsSSRF 防护:拦截内网 IP(10.x、172.16-31.x、192.168.x、127.x、::1)
API 请求express-validatorAPI Server 接口的参数校验(类型、长度、格式)

8. 超时控制与错误恢复

MCP 工具调用设置了超时机制,防止长时间挂起阻塞整个应用:

超时处理流程:
1. 发起工具调用时启动计时器(默认超时: 60 秒)
2. 超时触发:
a. 中止底层 MCP 连接请求
b. 向渲染进程返回超时错误
c. 记录错误日志
3. 错误恢复:
a. 检查 MCP Server 是否仍然存活
b. 若 Server 无响应: 尝试重启连接
c. 若连接无法恢复: 标记 Server 为不可用
4. 通知渲染进程:
通过 ToolListChangedNotification 通知工具列表状态变更

9. 工具列表变更通知

当 MCP Server 连接状态变化(新增、断开、工具注册/注销)时,MCPService 发送 ToolListChangedNotification 通知渲染进程:

通知流程:
MCP Server 状态变化
MCPService 触发 ToolListChangedNotification
渲染进程监听通知
更新 Redux store 中的工具列表
UI 自动刷新(可用工具、灰显不可用工具)

10. 工具调用名构建:buildFunctionCallToolName

为避免多个 MCP Server 的同名工具冲突,Cherry Studio 使用命名空间前缀构建工具调用名:

函数 buildFunctionCallToolName(serverName, toolName):
完整名称 = "{serverName}::{toolName}"
如果 完整名称 过长:
对 serverName 计算哈希前缀
完整名称 = "{hash_prefix}::{toolName}"
返回 完整名称

这种命名空间策略确保:

  • 不同 Server 的同名工具可以共存
  • 渲染进程的 toolPermissions 可以精确到 server::tool 粒度
  • 工具调用日志可以追溯来源 Server

问题与规避

1. 敏感字段遗漏

问题redactSensitive 仅匹配预设的字段名列表,如果 MCP Server 使用非标准命名(如 secretKeycredential),敏感信息可能泄露。

对策

  • 定期审查常用 MCP Server 的字段命名规范,扩展敏感字段列表
  • 对用户自定义 MCP Server 提供配置界面,允许手动添加敏感字段名
  • 在日志输出中对所有 string 类型值进行采样检查,发现疑似 Key/Token 格式时告警

2. 字符串截断导致工具失效

问题:300 字符的截断阈值可能导致某些工具(如长代码片段、大段文本)参数不完整,工具执行失败。

对策

  • 对特定工具配置更高的截断阈值(通过 toolPermissions 的 per-tool 配置)
  • 在工具 Schema 中声明最大输入长度,由 Schema 校验而非硬编码截断
  • 用户界面提示”参数过长可能导致调用失败”

3. OAuth 回调端口冲突

问题:随机端口可能与系统其他服务冲突,导致 OAuth 回调失败。

对策

  • 端口选择时检查可用性,重试最多 10 次
  • 回调超时后自动关闭服务器并返回错误
  • 用户可配置备用回调方式(如手动粘贴 authorization code)

4. SSRF 绕过风险

问题:仅检查 IP 地址可能无法防御 DNS Rebinding 攻击(域名先解析到公网 IP,再解析到内网 IP)。

对策

  • 在连接建立后再次验证目标 IP(TOCTOU 防护)
  • 使用 DNS-over-HTTPS 等安全 DNS 解析方式
  • 对频繁变更 DNS 记录的域名进行标记和拦截

5. 原型污染攻击

问题:恶意 MCP Server 可能返回包含 __proto__ 键的响应,试图污染 JavaScript 原型链。

对策

  • safeSerialize 中显式过滤 __proto__constructorprototype
  • 使用 Object.create(null) 创建无原型对象存储中间结果
  • 启用 Node.js 的 --disable-proto=throw 启动参数(如果可行)

6. IPC 输入注入

问题:渲染进程可能被恶意脚本注入,通过 IPC 向主进程发送非预期的工具调用。

对策

  • contextBridge.exposeInMainWorld 仅暴露最小必要 API
  • 主进程 ipcMain.handle 中对所有参数进行类型检查和 Schema 验证
  • 不信任渲染进程传递的任何数据,即使来自受信任的 preload 脚本

设计取舍

Electron IPC vs 直接函数调用

维度IPC 通信直接调用
安全性进程隔离,渲染进程崩溃不影响主进程同进程,漏洞可能直接影响主进程
性能序列化和进程间通信开销无额外开销
可调试性需要分别调试主进程和渲染进程统一调试
上下文隔离天然隔离(contextBridge需要手动实现隔离

Cherry Studio 的选择:采用 IPC 架构。MCP 工具运行在主进程中,通过 contextBridge 安全地向渲染进程暴露调用接口。这种选择牺牲了少量性能,但获得了更强的安全隔离——即使渲染进程被注入恶意代码,也无法直接访问 MCP Server 连接或文件系统。

前端权限 vs 后端权限

方案优势代价
仅前端(Redux)控制响应快、用户体验好渲染进程被绕过即可执行任意工具
仅后端(Main)控制安全性高、不可绕过每次调用需要跨进程查询,延迟增加
双层控制安全且体验好实现复杂度高,状态同步困难

Cherry Studio 的选择:双层控制。渲染进程的 toolPermissions 提供快速的用户体验(灰显未授权工具、即时提示),主进程在 ipcMain.handle 中再次验证权限和脱敏参数。即使前端被绕过,后端仍然有最后一道防线。

字符串截断 vs 完整传递

方案适用场景风险
固定截断(300 字符)Token 敏感场景、防止上下文溢出可能截断有效数据
动态截断(根据上下文窗口)充分利用上下文实现复杂,需要实时计算
完整传递短参数工具长参数可能导致 OOM 或 Token 耗尽

Cherry Studio 的选择:固定截断 300 字符 + …<N more> 提示。这种方案实现简单、行为可预测。截断提示让模型知道有内容被省略,模型可以选择是否需要完整内容。对于确实需要长参数的工具(如代码生成),应通过 per-tool 配置提高阈值。

严格 Schema 校验 vs 宽松校验

方案优势代价
严格校验(input + output)数据格式可控,防止恶意输入开发成本高,灵活度低
仅 input 校验实现简单,开发效率高输出可能包含未预期数据
无 Schema 校验零开发成本安全风险高,难以排查问题

Cherry Studio 的选择:input 和 output 双重 Schema 校验。MCPToolInputSchema 在调用前校验,MCPToolOutputSchema 在返回后校验。输出校验失败时不会直接拒绝(避免破坏用户体验),而是记录警告并通过 safeSerialize 安全序列化后返回,同时向开发者告警。


参考来源