工具调用与安全权限
工具调用与安全权限
学习目标
读完本章后,你将能够:
- 理解 Cherry Studio 中 MCP 工具调用的 Electron IPC 传递链路
- 描述
redactSensitive函数的脱敏策略与递归处理流程 - 掌握
toolPermissionsRedux slice 的用户权限控制机制 - 分析 MCP 工具输入输出的 Schema 验证与安全序列化
- 识别 MCP OAuth 认证流程中的安全加固点(回调服务器、URL 消毒、IP 验证)
- 应用 IPC 输入验证与超时控制的防御性编程实践
前置知识
- Electron 主进程 / 渲染进程架构与 IPC 通信模型(
contextBridge、ipcMain.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)。整体流程如下:
关键安全节点:
| 环节 | 防御措施 | 位置 |
|---|---|---|
| 渲染进程 → IPC | contextBridge 隔离上下文,仅暴露白名单 API | preload/ |
| 主进程接收 | 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 工具独立配置
allowed或denied - 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_PARAMS、INTERNAL_ERROR、TOOL_NOT_FOUND 等,确保错误信息在客户端和服务端之间一致。
5. 安全序列化:safeSerialize
MCP 工具执行结果在返回给渲染进程之前,需要通过 safeSerialize 进行安全序列化,防止:
- 原型污染:恶意对象通过
__proto__修改 JavaScript 原型链 - 循环引用:工具返回包含循环引用的对象导致序列化崩溃
- 特殊类型注入:
Date、RegExp、BigInt等非 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_token、refresh_token - 执行 PKCE(Proof Key for Code Exchange)流程
- Token 过期自动续期
7. URL 安全消毒与 IP 验证
当 MCP 工具涉及 URL 操作时,Cherry Studio 使用多层防护:
防护措施:
| 层级 | 工具 | 防护目标 |
|---|---|---|
| URL 格式 | strict-url-sanitise | 协议白名单(仅 http/https)、畸形 URL 拦截 |
| IP 地址 | ipaddr.js | SSRF 防护:拦截内网 IP(10.x、172.16-31.x、192.168.x、127.x、::1) |
| API 请求 | express-validator | API 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 使用非标准命名(如 secretKey、credential),敏感信息可能泄露。
对策:
- 定期审查常用 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__、constructor、prototype键- 使用
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 安全序列化后返回,同时向开发者告警。
参考来源
- Cherry Studio MCPService.ts — MCP 服务核心实现,包含工具调用、脱敏、序列化逻辑
- Cherry Studio toolPermissions.ts — Redux 权限管理 Slice
- Cherry Studio OAuth Callback — OAuth 回调服务器实现
- 通用知识:工具调用协议与执行模型 — 工具系统四层架构、并行/串行策略、审批模型
- Electron contextIsolation 文档 — Electron 安全上下文隔离机制
- MCP Specification — Model Context Protocol 规范
- strict-url-sanitise — URL 安全消毒库
- ipaddr.js — IP 地址解析与验证库
- express-validator — Express API 请求验证中间件