SSRF 防护
AIDY 会在最终发起 upstream HTTP 请求时执行 SSRF 防护。它不是 route plugin,也不使用 egress_policy 命名,避免和已有 Egress Policy 插件混淆。
防护覆盖:
- 运行时转发到 upstream 的请求
- FetchUpstreamModels
- TestUpstreamModel
默认行为
如果静态配置和 tenant 都没有设置 SSRF 策略,系统内置默认值为:
[upstream.ssrf_protection]
enabled = true
allow_private_network = false
domain_list_mode = "blacklist"
domain_list = []
ip_list_mode = "blacklist"
ip_list = []
allowed_ports = []
含义:
- 默认启用 SSRF 防护
- 默认不允许访问私网、loopback、link-local、metadata 等非公网地址
- 默认没有域名/IP 黑白名单
- 默认允许所有端口
- DNS 解析后的 IP 过滤始终开启,没有单独配置项
策略来源
SSRF 策略有两层来源:
- 静态配置
[upstream.ssrf_protection]提供全局默认策略 - tenant 资源的
ssrf_protection可以覆盖该 tenant 的整套策略
tenant 未设置 ssrf_protection 时继承静态默认。tenant 一旦设置 ssrf_protection,会整套覆盖静态默认,不做字段级 merge。
Provider 是 deprecated 兼容资源,不参与 SSRF 策略计算。
静态配置
[upstream.ssrf_protection]
enabled = true
allow_private_network = false
domain_list_mode = "blacklist"
domain_list = []
ip_list_mode = "blacklist"
ip_list = []
allowed_ports = []
字段说明:
| 字段 | 说明 |
|---|---|
enabled | 是否启用 SSRF 防护;为 false 时跳过检查 |
allow_private_network | 是否允许访问私网、loopback、link-local、metadata 等非公网地址 |
domain_list_mode | blacklist 或 whitelist |
domain_list | 域名名单,支持精确域名和 *.example.com |
ip_list_mode | blacklist 或 whitelist |
ip_list | IP 名单,支持 IPv4、IPv6、单 IP 和 CIDR |
allowed_ports | 允许的端口;空数组表示允许全部,支持单端口和 8000-8999 范围 |
Tenant 覆盖
tenant 资源上可以设置 core.SSRFProtectionConfig:
{
"id": "tn_01ARZ3NDEKTSV4RRFFQ69G7AA0",
"name": "demo",
"ssrf_protection": {
"enabled": true,
"allow_private_network": true,
"domain_list_mode": "blacklist",
"domain_list": [],
"ip_list_mode": "blacklist",
"ip_list": ["169.254.169.254", "fd00::/8"],
"allowed_ports": ["80", "443", "8000-8999"]
}
}
注意:tenant 配置是整套覆盖。上例设置后,这个 tenant 不再继承静态配置里的 domain/IP/port 列表。
检查规则
当某个 upstream 没有生效代理时,AIDY 会按以下顺序检查最终请求目标:
- 只允许
http和https - 检查 host 和端口
- 域名请求先匹配 domain list,再解析 DNS
- 直接 IP host 和域名解析出的 IP 都进入 IP 检查
- 所有 DNS 解析结果都必须通过检查,任一 IP 不合规都会拒绝本次请求
allow_private_network=false时拒绝非公网地址allow_private_network=true时允许非公网地址,但仍然受 IP 黑白名单和端口限制约束
非公网地址包括但不限于:RFC1918 私网、loopback、link-local、metadata 地址、保留地址、组播地址和 IPv6 ULA/link-local 等范围。
名单规则
Domain list
domain_list 支持两种写法:
domain_list = ["api.example.com", "*.trusted.example.com"]
api.example.com只匹配这个精确域名*.trusted.example.com匹配其子域名,例如a.trusted.example.com- 通配符只支持
*.前缀形式
IP list
ip_list 支持单 IP 和 CIDR,IPv4 / IPv6 都可用:
ip_list = ["203.0.113.10", "10.0.0.0/8", "2001:db8::/32"]
Allowed ports
allowed_ports 为空时允许所有端口;非空时只允许列出的端口或范围:
allowed_ports = ["80", "443", "8000-8999"]
代理行为
如果 upstream 生效的 proxy_url 非空且不是 "-",AIDY 会跳过 SSRF 检查,由代理侧承担网络访问控制。
proxy_url: "http://proxy.internal:8080" # 跳过 SSRF 检查
proxy_url: "socks5://127.0.0.1:1080" # 跳过 SSRF 检查
proxy_url: "-" # 显式直连,仍执行 SSRF 检查
proxy_url: "" # 未配置代理,执行 SSRF 检查
拦截结果
运行时 upstream 请求被 SSRF 防护拦截时,AIDY 返回 403 Forbidden,body 会包含明确原因。例如:
ssrf target blocked; IP is not public; host=127.0.0.1; port=8080; resolved_ips=127.0.0.1; rule=private_network
同一条错误也会写入 request log 的 upstream_requests[].meta.error 字段,避免退化成泛化的 transport error。多 upstream 重试时,每个被拦截的 attempt 都会记录独立的 upstream request;如果所有候选都被拦截,最终响应会保留 SSRF 拦截原因。
Management API 的行为:
- FetchUpstreamModels被拦截时返回 `PermissionDenied`
- TestUpstreamModel被拦截时在 response 的 `error_message` 中写入明确原因