Skip to content
源码分析手册

MCP 安全与信任模型:三层防御体系

由于 MCP Server 拥有操作外部系统的能力(如删除 GitHub 仓库、读取敏感文件),Claude Code 为其设计了一套严密的安全体系,确保第三方插件不会成为系统的“后门”。

本质

MCP 安全模型本质上是 基于能力清单的细粒度访问控制(Capabilities-based ACL)

它不信任任何新加入的插件。它通过 src/services/mcp/channelPermissions.ts,在模型与 Server 之间建立了一个双向的审计闸门。即使模型决定调用某个 MCP 工具,该调用也必须通过静态规则、动态白名单以及用户实时审批这三层考验。其核心理念是“最小特权原则”:Server 只能看到它被允许看到的路径,执行它被显式授权的操作。

从源码看实现

防御体系由以下三个核心层级组成:

  1. 静态管道审计 (Channel Allowlist): 利用 src/services/mcp/channelAllowlist.ts,系统限制了 Server 可以声明的能力范围。这在加载 Server 阶段就已生效,任何试图声明越权能力(如未在配置中注明的工具前缀)的行为都会被拦截。

  2. 动态动作拦截 (Dynamic Interception): 在 src/services/mcp/channelPermissions.ts 中,系统会检查每一次 mcp_call

    • 工具名审计:识别高危操作(如带有 destructive 标记的工具)。
    • 参数校验:利用 Zod 定义对传递给 Server 的数据进行严格验证,防止注入攻击。
    • 作用域检查:如果工具涉及文件操作,会验证路径是否在当前工作空间或显式信任的路径列表中。
  3. 用户在环审批 (User-in-the-loop): 当 MCP 工具执行涉及敏感操作(或在受限模式下)时,会触发 REPL 界面上的交互。

    • 审批对话框:展示 Server 名称、调用的具体工具及参数详情。
    • 持久化授权:用户可以选择“始终允许”,该选项会被记录在 settings.jsondontAsk 列表中,由 src/hooks/useCanUseTool.tsx 在后续调用中静默通过。
  4. 进程与沙箱隔离 (Isolation): 通过 Stdio 传输层,Server 与 Client 在物理进程上完全隔离。即使 Server 内部逻辑被攻破,它也无法直接窃取 Claude Code 进程中的 API Key 或 会话历史(除非这些信息被作为参数显式传给它)。

使用时的关键约束

  • 全家桶风险:如果你运行了一个恶意的、具有 Stdio 权限的 MCP Server,它理论上可以尝试读取其物理进程能访问的所有本地文件。请务必只从可信源安装插件。
  • 权限冒泡:MCP 的授权请求通常会冒泡到主终端。在 auto 模式下,系统会尝试自动审批,但这取决于你的 dontAsk 设置和 Server 的信任等级。
  • 配置优先级:项目级(Local)的 .mcp.json 往往比全局更受限制,因为它直接继承了工作区的信任(Workspace Trust)状态。如果工作区未受信任,某些敏感的 MCP 操作将被直接阻断。
  • 审计日志:所有的 MCP 调用和权限决策都会记录在诊断日志中(utils/diagLogs.ts),这对于排查隐蔽的安全策略冲突非常有用。

继续探索

源码锚点

  • claude-code-opensource/src/services/mcp/channelPermissions.ts — 核心权限裁决逻辑实现。
📄 src/services/mcp/channelPermissions.ts — 核心权限裁决逻辑实现。L36-38 of 241
typescript
export function isChannelPermissionRelayEnabled(): boolean {
  return getFeatureValue_CACHED_MAY_BE_STALE('tengu_harbor_permissions', false)
}
  • claude-code-opensource/src/services/mcp/channelAllowlist.ts — 静态白名单与能力限制。
📄 src/services/mcp/channelAllowlist.ts — 静态白名单与能力限制。L23-26 of 77
typescript
export type ChannelAllowlistEntry = {
  marketplace: string
  plugin: string
}
  • claude-code-opensource/src/hooks/useCanUseTool.tsx — 衔接 UI 层的审批判断钩子。
📄 src/hooks/useCanUseTool.tsx — 衔接 UI 层的审批判断钩子。L27-56 of 204
tsx
export type CanUseToolFn<Input extends Record<string, unknown> = Record<string, unknown>> = (tool: ToolType, input: Input, toolUseContext: ToolUseContext, assistantMessage: AssistantMessage, toolUseID: string, forceDecision?: PermissionDecision<Input>) => Promise<PermissionDecision<Input>>;
function useCanUseTool(setToolUseConfirmQueue, setToolPermissionContext) {
  const $ = _c(3);
  let t0;
  if ($[0] !== setToolPermissionContext || $[1] !== setToolUseConfirmQueue) {
    t0 = async (tool, input, toolUseContext, assistantMessage, toolUseID, forceDecision) => new Promise(resolve => {
      const ctx = createPermissionContext(tool, input, toolUseContext, assistantMessage, toolUseID, setToolPermissionContext, createPermissionQueueOps(setToolUseConfirmQueue));
      if (ctx.resolveIfAborted(resolve)) {
        return;
      }
      const decisionPromise = forceDecision !== undefined ? Promise.resolve(forceDecision) : hasPermissionsToUseTool(tool, input, toolUseContext, assistantMessage, toolUseID);
      return decisionPromise.then(async result => {
        if (result.behavior === "allow") {
          if (ctx.resolveIfAborted(resolve)) {
            return;
          }
          if (feature("TRANSCRIPT_CLASSIFIER") && result.decisionReason?.type === "classifier" && result.decisionReason.classifier === "auto-mode") {
            setYoloClassifierApproval(toolUseID, result.decisionReason.reason);
          }
          ctx.logDecision({
            decision: "accept",
            source: "config"
          });
          resolve(ctx.buildAllow(result.updatedInput ?? input, {
            decisionReason: result.decisionReason
          }));
          return;
        }
        const appState = toolUseContext.getAppState();
        const description = await tool.description(input as never, {
  • claude-code-opensource/src/utils/mcpValidation.ts — 对 MCP 协议交互数据的底层校验。
📄 src/utils/mcpValidation.ts — 对 MCP 协议交互数据的底层校验。L14-43 of 209
typescript
export const MCP_TOKEN_COUNT_THRESHOLD_FACTOR = 0.5
export const IMAGE_TOKEN_ESTIMATE = 1600
const DEFAULT_MAX_MCP_OUTPUT_TOKENS = 25000

/**
 * Resolve the MCP output token cap. Precedence:
 *   1. MAX_MCP_OUTPUT_TOKENS env var (explicit user override)
 *   2. tengu_satin_quoll GrowthBook flag's `mcp_tool` key (tokens, not chars —
 *      unlike the other keys in that map which getPersistenceThreshold reads
 *      as chars; MCP has its own truncation layer upstream of that)
 *   3. Hardcoded default
 */
export function getMaxMcpOutputTokens(): number {
  const envValue = process.env.MAX_MCP_OUTPUT_TOKENS
  if (envValue) {
    const parsed = parseInt(envValue, 10)
    if (Number.isFinite(parsed) && parsed > 0) {
      return parsed
    }
  }
  const overrides = getFeatureValue_CACHED_MAY_BE_STALE<Record<
    string,
    number
  > | null>('tengu_satin_quoll', {})
  const override = overrides?.['mcp_tool']
  if (
    typeof override === 'number' &&
    Number.isFinite(override) &&
    override > 0
  ) {
  • claude-code-opensource/src/types/permissions.ts — 权限相关的数据模型定义。
📄 src/types/permissions.ts — 权限相关的数据模型定义。L16-45 of 442
typescript
export const EXTERNAL_PERMISSION_MODES = [
  'acceptEdits',
  'bypassPermissions',
  'default',
  'dontAsk',
  'plan',
] as const

export type ExternalPermissionMode = (typeof EXTERNAL_PERMISSION_MODES)[number]

// Exhaustive mode union for typechecking. The user-addressable runtime set
// is INTERNAL_PERMISSION_MODES below.
export type InternalPermissionMode = ExternalPermissionMode | 'auto' | 'bubble'
export type PermissionMode = InternalPermissionMode

// Runtime validation set: modes that are user-addressable (settings.json
// defaultMode, --permission-mode CLI flag, conversation recovery).
export const INTERNAL_PERMISSION_MODES = [
  ...EXTERNAL_PERMISSION_MODES,
  ...(feature('TRANSCRIPT_CLASSIFIER') ? (['auto'] as const) : ([] as const)),
] as const satisfies readonly PermissionMode[]

export const PERMISSION_MODES = INTERNAL_PERMISSION_MODES

// ============================================================================
// Permission Behaviors
// ============================================================================

export type PermissionBehavior = 'allow' | 'deny' | 'ask'

基于 Claude Code v2.1.88 开源快照的深度分析