Skip to content

6.3 网络与 SSRF 防护

本节你会学到

  • 为什么 Agent 是天然的 SSRF 探针,以及默认黑名单覆盖什么
  • 4 层网络防御:域名规则 → 重定向阻断 → egress 防火墙 → DNS rebinding
  • 生产模式:VPC egress 白名单、web_fetch 禁重定向

Agent 能访问网络的三个工具:web_fetchweb_search、以及通过 MCP 的各种服务。本节讲如何把它们的访问面缩到最小必要

三层网络防线

   LLM → web_fetch / web_search / MCP


  ┌──────────────────────────────┐
  │ 层 1: PermissionEngine 域名  │  .github.com 允 / 127.0.0.1 拒
  │        allowlist / blocklist  │
  └──────────────────────────────┘


  ┌──────────────────────────────┐
  │ 层 2: HTTP 客户端(httpx)    │  TLS、超时、重定向策略
  └──────────────────────────────┘


  ┌──────────────────────────────┐
  │ 层 3: 网络边界                │  VPC / egress rules / firewall
  │        (基础设施级)           │
  └──────────────────────────────┘

层 1 · 域名规则(权限引擎)

WORKSPACE_WRITE 预设自带的 SSRF 黑名单值得每个项目都保留+扩展

json
{
  "tool": "web_fetch",
  "domain": {
    "blocklist": [
      "localhost",
      "127.0.0.1",
      "0.0.0.0",
      "169.254.169.254",   // AWS/GCP 元数据服务
      ".internal",
      ".local",
      "::1"                 // IPv6 localhost
    ]
  },
  "action": "deny"
}

生产环境建议扩展

json
{
  "tool": "web_fetch",
  "domain": {
    "blocklist": [
      "localhost", "127.0.0.1", "0.0.0.0", "169.254.169.254",
      "::1", ".internal", ".local",
      // 你自己的内网网段(字面量,因为 IP 没法做后缀匹配)
      "10.", "172.16.", "192.168.",   // 注意:这些只会匹配 URL 里的 literal,IP 匹配不完整
      // 你的 SaaS 公司内部域名
      ".corp.your-company.com",
      ".internal.your-company.com",
      // 云厂商 metadata
      "metadata.google.internal",
      "metadata.azure.com"
    ]
  },
  "action": "deny"
}

⚠️ 局限_extract_domain 从 URL 提取 hostname,纯字符串前缀匹配。攻击者可以用十进制 IP(如 http://2130706433,等于 127.0.0.1)、IPv6 形式DNS rebinding 绕过。生产环境必须加层 3(基础设施级网络隔离)兜底。

限制到 allowlist 的保守模式

更安全的做法是默认拒绝,只显式允许你需要的域:

json
{
  "rules": [
    {"tool": "web_fetch", "domain": {"allowlist": [".your-docs-site.com", ".github.com"]}, "action": "allow"},
    {"tool": "web_fetch", "action": "deny"}
  ]
}

客户端产品(Agent 帮用户做事)一般要 blocklist;内部工具(Agent 做研究)可用更开放的 allowlist。

层 2 · HTTP 客户端行为

Agentao 的 web_fetch 使用 httpx,默认:

  • 10 秒超时
  • 跟随 3 次重定向
  • TLS 验证开启
  • User-Agent 可定制

安全注意:允许重定向 = 允许 302 跳转到内网地址绕过 hostname 检查。生产上建议禁止重定向每次重定向重新跑域名规则。目前 Agentao 未做"重定向后重检"——这是已知限制。

你可以自定义 web_fetch(替代内置)来加严:

python
from agentao.tools.base import Tool
import httpx

class StrictWebFetchTool(Tool):
    @property
    def name(self) -> str:
        return "web_fetch"

    def execute(self, url: str, **kw) -> str:
        with httpx.Client(follow_redirects=False, timeout=5.0) as client:
            resp = client.get(url)
            if resp.status_code // 100 == 3:
                return "Redirects are disabled for security. URL: " + url
            return resp.text[:50000]   # 限长
    # ...省略其他方法

extra_tools= 注入——同名条目会静默替换内置 web_fetch,并和内置一样获得能力绑定(working_directory / filesystem / shell):

python
agent = Agentao(..., extra_tools=[StrictWebFetchTool()])   # 替换内置 web_fetch

优先用这个,而不是构造后去戳 agent.tools.register(StrictWebFetchTool()):底层路径会绕过能力绑定(工具变「裸」),且冲突只是 warn。见 5.1。schema 替换是纵深防御,不是边界——真正拦住外联的是下面的层 3。

层 3 · 基础设施级隔离

这是兜底层——哪怕前面所有层都失效,Agent 也够不到危险的东西

容器的 network 选项

bash
# 完全无网:Agent 只能靠 MCP stdio 之类的本地服务
docker run --network=none agent-image

# 自定义网络:只允许出站到白名单
docker run --network=custom-egress-only agent-image

VPC egress 白名单

云上给 Agent 容器绑定一个 egress security group,只允许出站到:

  • LLM API(OpenAI / Anthropic 官方 IP 段)
  • 必要的 MCP SSE 端点
  • 白名单文档站点(.github.com, .pypi.org 等)

禁止一切其他出站。这样哪怕规则引擎被绕过,LLM 请求也到不了内网。

DNS 层过滤

用公司 DNS 做内网域名黑名单——Agent 的 hostname 解析请求被 DNS 拒绝,直接连不上。

MCP 服务器的网络

MCP 服务器通常比 web_fetch 风险更高——它们有自己的凭据、自己的访问面:

json
{
  "mcpServers": {
    "database": {
      "command": "...",
      "env": {"DB_URL": "postgres://..."}   // 数据库访问
    }
  }
}

控制策略

  1. 每租户独立 MCP 实例 —— 凭据按租户隔离(参见 5.3
  2. MCP 子进程跑在独立网络命名空间 —— Linux 上用 unshare -n 或容器
  3. 把 MCP 工具也纳入权限规则
json
{
  "rules": [
    {"tool": "mcp_database_query", "args": {"sql": "^SELECT "}, "action": "allow"},
    {"tool": "mcp_database_*", "action": "deny"}
  ]
}

ACP 模式的网络考量

Agentao 作为 ACP Server 时不监听端口——只用 stdio。这是好消息:

  • 宿主不需要为 Agent 开 inbound 端口
  • 网络攻击面缩到出站方向

但 Agent 的 LLM 调用、web_fetch、MCP SSE 还是会出站。同样适用上面层 1-3 的策略。

审计日志

每次网络访问都应进日志:

python
# 在 on_event 里监听 TOOL_COMPLETE
def on_event(ev):
    if ev.type == EventType.TOOL_COMPLETE and ev.data["tool"] in {"web_fetch", "web_search"}:
        audit_log.info("network_call", extra={
            "tool": ev.data["tool"],
            "status": ev.data["status"],
            "duration_ms": ev.data["duration_ms"],
            "call_id": ev.data["call_id"],
            # 从别处查到 URL(比如 TOOL_START 时存一下)
        })

agentao.log 默认已经记录工具调用的完整参数——日志脱敏请看 6.5 密钥管理

⚠️ 常见陷阱

上线前先确认这几条

  • 只有 allowlist 没有 blocklist —— *.example.com 放行,但 169.254.169.254 没禁,重定向后还是中招
  • 相信 LLM 不会去访问内网 —— 系统提示扛不住 Prompt 注入,必须在规则层强制
  • 重定向未受保护 —— https://good.com → 302 → http://169.254.169.254/ 默认会跟随

下面每一条都附完整修法。

❌ 只有 allowlist 没有 blocklist

json
{"tool": "web_fetch", "domain": {"allowlist": [".github.com"]}, "action": "allow"}
// 缺 blocklist → 其他 URL 走到了默认 ASK → 用户可能点同意访问内网

补上明确的 blocklist + 默认 deny 才稳。

❌ 相信 LLM 不会去访问内网

Prompt injection 可以LLM 访问任何 URL。不要依赖 LLM 的"常识",依赖规则和基础设施。

❌ 重定向未受保护

web_fetch https://good.com → 302 → http://169.254.169.254/ 会被内置 httpx 跟随。生产上考虑用自定义 web_fetch 禁重定向。

TL;DR

  • 默认 SSRF 黑名单已覆盖 localhost、127.0.0.1169.254.169.254(云元数据)、RFC1918 私网。不要禁用它
  • 第 4 层(规则引擎)是应用侧;第 7 层(VPC / egress 防火墙)是基础设施侧——两者都要。应用可被骗,基础设施才是硬墙。
  • 生产环境 web_fetch 永远禁重定向——https://good.com → 302 → 云元数据 IP 是经典绕过手法。
  • 业务真要访问的内部 API,写显式 allowlist;不要去放宽全局黑名单。

6.4 多租户隔离与文件系统