Web 搜索多提供商与 SSRF 防护
Web 搜索多提供商与 SSRF 防护
学习目标
本章聚焦 LibreChat 的 Web 搜索系统。你将了解:
- 多提供商搜索的认证与聚合策略
- SSRF 防护的 fail-closed 设计
- 用户提供 URL 的验证流程
前置知识
下文假设你已理解上述概念,直接聚焦 LibreChat 的具体实现。
项目实践
多提供商认证
loadWebSearchAuth 函数遍历所有配置的搜索类别(search、scraper、reranker),验证每个提供商的认证信息:
for each category-service combination: for each auth field: if value is ${ENV_VAR} 形式: 从 DB 加载用户提供的值 else: 使用系统定义的值 if at least one service in category is authenticated: category.authenticated = true认证类型分为两种:
| 类型 | 含义 |
|---|---|
USER_PROVIDED | 使用用户自己配置的 API Key 或 URL |
SYSTEM_DEFINED | 使用管理员在 .env 或 YAML 中配置的值 |
SSRF 防护
isSSRFUrl 函数实现了 fail-closed 的 SSRF 防护:
isSSRFUrl(url): try: parsed = new URL(url) catch: return true // 无法解析 → 阻止
if parsed.protocol 不是 http/https: return true // 非 HTTP 协议 → 阻止
if isSSRFTarget(parsed.hostname): return true // 已知内部地址 → 阻止
return resolveHostnameSSRF(parsed.hostname) // DNS 解析检查防护层级:
- URL 解析失败 → 阻止(防畸形 URL 绕过)
- 非 HTTP(S) 协议 → 阻止(防
file://、gopher://等) - 已知内部地址黑名单 → 阻止(
127.0.0.1、169.254.169.254等) - DNS 解析检查 → 阻止 DNS rebinding 攻击
用户提供 URL 的验证
两类 URL 字段的处理策略不同:
| 类型 | 处理方式 |
|---|---|
普通用户提供 URL(如 searxngInstanceUrl) | SSRF 预检通过后允许 |
需管理员启用的 URL(如 tavilySearchUrl) | 需要 USER_PROVIDED_OPT_IN_URL_KEYS 中的显式启用 |
这给了管理员对敏感 URL 字段的精细控制——即使配置允许用户提供值,也需要管理员显式启用。
问题与规避
问题 1:用户配置的 URL 指向内部服务
规避:SSRF 防护的 resolveHostnameSSRF 函数对 hostname 进行 DNS 解析,检查解析结果是否为内部地址。这防止了 DNS rebinding 攻击——即使 URL 的 hostname 看起来是外部地址,DNS 解析后可能指向内部 IP。
问题 2:无法解析的 URL 格式导致异常
规避:isSSRFUrl 在 try-catch 中解析 URL,解析失败返回 true(阻止),不会抛出异常。
问题 3:认证字段包含未解析的环境变量占位符
规避:extractWebSearchEnvVars 函数提取 ${VAR} 格式的变量名,在认证前检查环境变量是否存在。缺失的变量导致认证失败,而非传递未解析的占位符。
设计取舍
Fail-Closed vs Fail-Open SSRF 防护
| 方案 | 优势 | 代价 |
|---|---|---|
| Fail-Closed(默认阻止) | 安全,不会误放内部地址 | 可能阻止合法的外部 URL |
| Fail-Open(默认允许) | 不会误拦 | 安全风险高 |
LibreChat 选择了 fail-closed,因为搜索系统处理的是用户输入的 URL,安全优先级高于可用性。