生产环境 MCP 安全:把防线从网关延伸到执行、出站与语义层

2026-07-29 15 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:13 分钟

MCP 网关可以完成身份认证、限流和请求审计,但它看不到全部风险:工具可能在网关放行后执行危险命令,连接器可能把数据发送到不受信任的地址,模型也可能被提示注入诱导,以合法身份完成不合法的操作。

生产级 MCP 安全因此不能只依赖入口。更稳健的思路是纵深防御,并在每类风险最早出现、且仍可被可靠判断的位置实施控制。可以把这套架构拆成四层:安全执行、管理基础设施、出站信任和语义完整性。

网关之后仍然存在什么风险

网关擅长判断“谁可以调用哪个 MCP 服务”,却很难独自回答下面这些问题:

  • filesystem.write_file 的目标路径是否越过工作目录?
  • shell.run 是否启动了未授权程序,或者通过参数绕过命令白名单?
  • 一个原本可信的 HTTP 工具是否在重定向后访问了内网元数据地址?
  • 模型调用 send_email 时,收件人和附件是否符合当前任务意图?
  • MCP Server 的工具描述是否在部署后被替换或篡改?

这些问题发生在不同边界上。把所有判断塞进网关,会迫使网关理解文件系统、进程、网络、凭据和自然语言意图,最终形成一个庞大但仍有盲区的策略点。

“最早可信控制点”是更实用的原则:文件路径应在打开文件前检查,出站地址应在建立连接前检查,高风险工具调用应在执行前结合参数和上下文判断。控制越接近实际动作,越不容易被编码、重定向或协议转换绕过。

四层控制如何分工

1. 安全执行:限制工具真正能做什么

这一层围绕 MCP Server 及其工具运行时展开。关键措施包括:

  • 以非 root 用户运行工具,并使用只读根文件系统。
  • 每个 Server 使用独立容器、沙箱或受限进程,避免共享宿主机权限。
  • 只挂载必要目录,并明确区分只读与可写路径。
  • 对命令、文件路径、参数大小、执行时间和输出大小设置硬限制。
  • 不把云平台长期凭据、SSH 密钥或整个用户主目录注入工具环境。

参数校验不能只检查字符串前缀。例如,检查路径是否以 /workspace 开头,挡不住符号链接和 ../。执行层应先解析规范路径,再确认它仍位于允许目录中。

2. 管理基础设施:控制身份、配置和生命周期

MCP Server 不应被当作随意启动的本地插件。进入生产后,它需要具备普通服务应有的管理能力:

  • 工作负载身份与短期凭据,而不是共享 API Key。
  • Server、工具清单和策略配置的版本管理与审批。
  • 部署产物签名、镜像摘要固定和依赖来源验证。
  • 集中审计日志,并记录主体、会话、工具、参数摘要、策略结果和执行结果。
  • 可快速撤销单个工具、Server 或身份权限的开关。

审计日志应避免直接保存密码、令牌和完整敏感文档。更合适的做法是记录参数类型、目标资源、内容哈希和脱敏摘要,同时保留关联请求所需的 trace ID。

3. 出站信任:不要默认相信工具访问的目标

很多 MCP 工具的价值来自外部连接,例如访问 Git 仓库、工单系统、数据库或 SaaS API。也正因为如此,出站流量是数据泄露、SSRF 和凭据滥用的重要通道。

出站控制应同时覆盖 DNS、IP、端口、TLS 身份和应用层目标:

  • 默认拒绝外连,只允许业务必需的域名或服务身份。
  • 阻止环回地址、链路本地地址、云元数据地址和未授权内网网段。
  • 限制自动重定向,并对每次重定向后的目标重新执行策略。
  • 使用短期、按目标绑定的凭据,避免一个令牌可访问多个系统。
  • 通过受控 egress proxy 统一记录目标、响应码和传输字节数。

仅配置域名白名单还不够。DNS 重绑定、代理行为和服务端重定向都可能改变最终连接目标,因此网络层和应用层需要共同执行限制。

4. 语义完整性:判断“允许的调用”是否符合任务意图

语义层处理的是传统访问控制不擅长的问题:调用者有权限,工具本身也合法,但这次组合起来是否合理。

例如,读取一个公开工单后发送摘要可能是正常流程;读取凭据文件,再把内容发到工单评论,则应被阻止。可以围绕以下信号建立策略:

  • 工具调用链是否出现异常组合。
  • 参数中的目标用户、仓库、域名或数据分类是否超出任务范围。
  • 工具描述、输入 schema 或 Server 身份是否与批准版本一致。
  • 高风险动作是否经过确定性的确认或人工审批。
  • 来自网页、文档和邮件的内容是否被错误地当成系统指令。

不要让另一个模型成为唯一的语义安全裁判。模型分类适合补充风险评分,但转账、删除、发布、发送外部邮件等动作仍应由确定性规则和审批机制兜底。

可以这样实践:执行前策略检查

下面是一个可直接运行的最小 Python 示例。它不依赖特定 MCP SDK,假设 MCP Host 在调用工具前把工具名和参数交给 authorize。实际接入时,可把同样的检查放进 Host 中间件、Server 包装器或独立策略服务。

将代码保存为 policy_check.py,然后运行 python policy_check.py

from pathlib import Path
from urllib.parse import urlparse
import ipaddress
import socket

WORKSPACE = Path("/tmp/mcp-workspace").resolve()
ALLOWED_HOSTS = {"api.github.com", "tickets.example.com"}
ALLOWED_TOOLS = {"read_file", "write_file", "http_get"}


def path_is_allowed(raw_path: str) -> bool:
    candidate = Path(raw_path).expanduser().resolve(strict=False)
    return candidate == WORKSPACE or WORKSPACE in candidate.parents


def host_is_public(hostname: str) -> bool:
    try:
        addresses = socket.getaddrinfo(hostname, None)
    except socket.gaierror:
        return False

    for address in addresses:
        ip = ipaddress.ip_address(address[4][0])
        if not ip.is_global:
            return False
    return True


def authorize(tool: str, arguments: dict) -> tuple[bool, str]:
    if tool not in ALLOWED_TOOLS:
        return False, "tool is not allowlisted"

    if tool in {"read_file", "write_file"}:
        if not path_is_allowed(str(arguments.get("path", ""))):
            return False, "path escapes the workspace"

    if tool == "http_get":
        parsed = urlparse(str(arguments.get("url", "")))
        if parsed.scheme != "https" or parsed.hostname not in ALLOWED_HOSTS:
            return False, "outbound destination is not approved"
        if not host_is_public(parsed.hostname):
            return False, "destination resolves to a non-public address"

    return True, "allowed"


if __name__ == "__main__":
    tests = [
        ("read_file", {"path": "/tmp/mcp-workspace/report.txt"}),
        ("read_file", {"path": "/etc/passwd"}),
        ("http_get", {"url": "https://api.github.com/repos/example/demo"}),
        ("http_get", {"url": "http://169.254.169.254/latest/meta-data/"}),
        ("shell", {"command": "curl https://unknown.example"}),
    ]

    for tool, arguments in tests:
        allowed, reason = authorize(tool, arguments)
        print(f"{tool:10} allowed={allowed:<5} reason={reason}")

这个示例展示了控制点的位置,但生产实现还需要补足几个边界:

  • 在真正建立连接的组件中再次校验解析后的 IP,降低 DNS 重绑定风险。
  • 禁止或逐跳验证 HTTP 重定向。
  • 写文件时使用避免符号链接竞态的安全打开方式。
  • 将策略决定、主体身份和 trace ID 写入审计系统。
  • 给策略查询设置超时,并明确超时后默认拒绝还是进入受限降级模式。

Kubernetes 中把出站默认值设为拒绝

如果 MCP Server 运行在 Kubernetes,可以先用 NetworkPolicy 建立默认拒绝基线。下面的 YAML 假设集群网络插件支持 NetworkPolicy,并且 DNS 位于带有 k8s-app: kube-dns 标签的命名空间和 Pod 中;部署前需要按实际集群标签调整。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mcp-default-deny-egress
  namespace: mcp
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/part-of: mcp
  policyTypes:
    - Egress
  egress: []
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mcp-allow-dns
  namespace: mcp
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/part-of: mcp
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

应用并检查策略:

kubectl apply -f mcp-network-policy.yaml
kubectl -n mcp get networkpolicy
kubectl -n mcp describe networkpolicy mcp-default-deny-egress

标准 NetworkPolicy 主要按 IP 和端口工作,不适合直接表达稳定的域名白名单。生产环境通常还需要 egress gateway、服务网格、CNI 扩展能力或显式代理来验证域名与 TLS 身份。

落地顺序:先收窄爆炸半径,再增加语义判断

纵深防御不等于第一天部署所有复杂组件。可以按风险和可验证性逐步推进:

  1. 盘点所有 MCP Server、工具、凭据、可写目录和外部目标,删除无人负责的实例。
  2. 隔离执行环境,使用非 root、只读文件系统、最小挂载和资源上限。
  3. 将出站流量改为默认拒绝,只开放已确认的业务目标。
  4. 为每次工具调用记录身份、策略结果、目标和 trace ID,并对敏感参数脱敏。
  5. 对删除、发布、付款、外发和权限变更设置确定性规则与人工审批。
  6. 固定 Server 版本、工具 schema 和部署摘要,检测未经批准的变化。
  7. 用提示注入、路径穿越、SSRF、重定向和凭据泄露场景持续做回归测试。

网关仍然重要,但它应是多层控制中的入口,而不是唯一防线。真正可靠的生产架构会让每一层只回答自己最擅长的问题,并让高风险操作必须连续穿过身份、执行、网络和语义检查。即使某一层判断失误,其余层仍能限制影响范围并留下可调查的证据。


相关推荐