Skip to content

MCP 安全最佳实践

MCP 把能力开放给模型时,安全必须前置。

1. 最小权限

  • Tool 默认只读。
  • 写操作需要显式授权。
  • 高风险操作增加人工确认。

权限配置示例

yaml
# mcp-security-config.yaml
tools:
  # 默认权限
  default_permissions: read-only

  # 分级权限
  permission_levels:
    read:
      - 'search:*'
      - 'read:*'
      - 'list:*'

    write:
      - 'create:*'
      - 'update:*'
      - 'delete:*'

    elevated:
      - 'exec:*'
      - 'admin:*'
      - 'system:*'

  # 高风险操作配置
  high_risk_tools:
    - name: 'exec:shell'
      requires_approval: true
      approvers: [admin, security]
      timeout: 300

    - name: 'write:sensitive'
      requires_approval: true
      approvers: [admin]
      audit_level: verbose

2. 输入校验

  • 参数做白名单和类型校验。
  • 禁止原样拼接到系统命令。
  • 对外部 URL 做域名与协议限制。

输入校验代码示例

typescript
// input-validation.ts
import { z } from 'zod'

// 定义参数 Schema
const toolInputSchema = z.object({
  query: z.string().max(500).regex(/^[\w\s\-.,!?]+$/),
  limit: z.number().int().min(1).max(100).default(10),
  filters: z.record(z.string()).optional(),
})

// URL 白名单校验
const ALLOWED_DOMAINS = [
  'api.example.com',
  'cdn.example.com',
  'internal.example.com',
]

function validateUrl(url: string): boolean {
  try {
    const parsed = new URL(url)
    // 只允许 HTTPS
    if (parsed.protocol !== 'https:') {
      return false
    }
    // 检查域名白名单
    return ALLOWED_DOMAINS.some(
      domain => parsed.hostname === domain
        || parsed.hostname.endsWith(`.${domain}`)
    )
  }
  catch {
    return false
  }
}

// 命令注入防护
function sanitizeCommand(input: string): string {
  // 移除危险字符
  const dangerous = /[;&|`$(){}[\]<>]/g
  return input.replace(dangerous, '')
}

// 综合校验函数
function validateToolInput(
  toolName: string,
  input: unknown
): { valid: boolean, error?: string } {
  // 1. Schema 校验
  const result = toolInputSchema.safeParse(input)
  if (!result.success) {
    return { valid: false, error: result.error.message }
  }

  // 2. URL 校验(如果包含 URL)
  if (input.url && !validateUrl(input.url)) {
    return { valid: false, error: 'URL not in whitelist' }
  }

  // 3. 敏感词检测
  const sensitivePatterns = [
    /password/i,
    /secret/i,
    /api[_-]?key/i,
    /token/i,
  ]
  const inputStr = JSON.stringify(input)
  for (const pattern of sensitivePatterns) {
    if (pattern.test(inputStr)) {
      return { valid: false, error: 'Input contains sensitive patterns' }
    }
  }

  return { valid: true }
}

3. 输出约束

  • 敏感字段脱敏。
  • 限制单次返回数据量。
  • 防止泄露内部路径和系统细节。

输出处理示例

typescript
// output-sanitization.ts

// 敏感字段脱敏规则
const SENSITIVE_FIELDS = [
  'password',
  'apiKey',
  'api_key',
  'secret',
  'token',
  'credential',
]

// 脱敏函数
function redactSensitive(data: unknown, depth = 0): unknown {
  if (depth > 10) {
    return '[MAX_DEPTH]'
  } // 防止循环引用

  if (typeof data === 'string') {
    // 移除可能的路径信息
    return data
      .replace(/\/\S+\/\S+/g, '[PATH]')
      .replace(/[A-Z]:\\\S+/g, '[PATH]')
  }

  if (Array.isArray(data)) {
    return data.slice(0, 100).map(item => redactSensitive(item, depth + 1))
  }

  if (typeof data === 'object' && data !== null) {
    const result: Record<string, unknown> = {}
    for (const [key, value] of Object.entries(data)) {
      // 检查是否是敏感字段
      if (SENSITIVE_FIELDS.some(field =>
        key.toLowerCase().includes(field.toLowerCase())
      )) {
        result[key] = '[REDACTED]'
      }
      else {
        result[key] = redactSensitive(value, depth + 1)
      }
    }
    return result
  }

  return data
}

// 输出大小限制
const MAX_OUTPUT_SIZE = 100000 // 100KB

function limitOutputSize(data: unknown): { data: unknown, truncated: boolean } {
  const str = JSON.stringify(data)
  if (str.length <= MAX_OUTPUT_SIZE) {
    return { data, truncated: false }
  }

  // 截断并返回
  return {
    data: {
      message: 'Output truncated due to size limit',
      size: str.length,
      limit: MAX_OUTPUT_SIZE,
    },
    truncated: true,
  }
}

4. 审计与追踪

  • 记录谁在何时调用了哪个工具。
  • 记录调用参数摘要与结果状态。
  • 建立异常调用告警。

审计日志示例

typescript
// audit-logger.ts

interface AuditLog {
  timestamp: string
  requestId: string
  toolName: string
  userId: string
  inputHash: string // 不存储原文,只存哈希
  outputStatus: 'success' | 'error' | 'timeout'
  durationMs: number
  riskLevel: 'low' | 'medium' | 'high'
  ipAddress: string
  userAgent: string
}

class AuditLogger {
  private logs: AuditLog[] = []
  private maxLogs = 10000

  log(entry: Omit<AuditLog, 'timestamp'>): void {
    this.logs.push({
      ...entry,
      timestamp: new Date().toISOString(),
    })

    // 保持日志大小
    if (this.logs.length > this.maxLogs) {
      this.logs = this.logs.slice(-this.maxLogs)
    }
  }

  // 异常检测
  detectAnomalies(): string[] {
    const anomalies: string[] = []

    // 检测高频调用
    const recentCalls = this.logs.filter(
      log => Date.now() - new Date(log.timestamp).getTime() < 60000
    )
    if (recentCalls.length > 100) {
      anomalies.push('High call frequency detected')
    }

    // 检测高风险操作
    const highRiskCalls = recentCalls.filter(log => log.riskLevel === 'high')
    if (highRiskCalls.length > 10) {
      anomalies.push('High frequency of high-risk operations')
    }

    // 检测失败率
    const failRate = recentCalls.filter(log => log.outputStatus !== 'success').length / recentCalls.length
    if (failRate > 0.3) {
      anomalies.push(`High failure rate: ${(failRate * 100).toFixed(1)}%`)
    }

    return anomalies
  }

  // 导出日志
  export(): AuditLog[] {
    return [...this.logs]
  }
}

5. 红队测试

至少覆盖:

  • Prompt Injection 场景。
  • 越权调用场景。
  • 数据外泄场景。

红队测试清单

markdown
# MCP 安全测试清单

## Prompt Injection 测试

- [ ] 尝试覆盖系统指令
- [ ] 尝试获取系统提示词
- [ ] 尝试绕过权限检查
- [ ] 尝试注入恶意命令

## 越权测试

- [ ] 低权限用户调用高权限工具
- [ ] 尝试访问其他用户数据
- [ ] 尝试修改其他用户的配置
- [ ] 尝试绕过审批流程

## 数据泄露测试

- [ ] 尝试获取系统配置
- [ ] 尝试获取其他用户的密钥
- [ ] 尝试通过错误信息获取内部信息
- [ ] 尝试通过时序攻击获取信息

## DoS 测试

- [ ] 发送超大输入
- [ ] 发送大量并发请求
- [ ] 发送循环引用数据结构
- [ ] 发送超长字符串

## 渗透测试

- [ ] SQL 注入(如果使用数据库)
- [ ] 命令注入(如果执行系统命令)
- [ ] 路径遍历(如果访问文件系统)
- [ ] SSRF(如果发起外部请求)

6. 变更管理

  • 能力变更需要评审。
  • 高风险能力先灰度。
  • 保留快速回滚机制。

变更管理流程

yaml
# mcp-change-management.yaml

change_request:
  # 变更申请
  request:
    tool_name: 'exec:shell'
    change_type: modify # add | modify | remove
    risk_level: high
    description: Add timeout parameter
    impact_analysis: 'Affects all exec:shell calls'

  # 审批流程
  approval:
    required_approvers: 2
    approver_roles: [security, admin]
    timeout_hours: 24

  # 灰度发布
  rollout:
    stages:
      - name: internal
        scope: [dev_team]
        duration: 24h
        success_threshold: 99%

      - name: beta
        scope: [beta_users]
        duration: 72h
        success_threshold: 95%

      - name: production
        scope: [all_users]
        rollback_on:
          - success_rate < 90%
          - error_rate > 5%

  # 回滚条件
  rollback:
    automatic:
      - error_rate > 10%
      - p99_latency > 30s
    manual:
      - security_incident
      - data_leak_suspected