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: verbose2. 输入校验
- 参数做白名单和类型校验。
- 禁止原样拼接到系统命令。
- 对外部 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