AI SDK 安全 URL 处理规范:从 `validateUrl` 信任决策到 SSRF 防护的源码级解析

AI SDK 安全 URL 处理规范:从 `validateUrl` 信任决策到 SSRF 防护的源码级解析 AI SDK 安全 URL 处理规范从validateUrl信任决策到 SSRF 防护的源码级解析【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai本文基于 AI SDKThe AI Toolkit for TypeScript仓库中的安全规范文档 contributing/secure-url-handling.md并结合ai-sdk/provider-utils包源码系统讲解当 Provider 从响应体中拿到下载 URL 或轮询 URL 时AI SDK 如何通过getFromApi的validateUrl、trustedOrigin、credentialedOrigin三个显式选项完成服务端安全抓取。读完本文你将掌握 AI SDK 的 URL 信任决策模型、SSRF 防护链路逐跳校验 DNS 连接期绑定、自托管/本地部署场景的豁免机制以及如何在 CI 与运行时两个层面加固自己的 Provider 集成代码。背景为什么 Provider 返回的 URL 需要被信任校验许多模型 Provider 会在响应体中直接返回一个 URL——例如 fal 的生成图片/视频/语音下载地址image.url、audio.url、video.url、MiniMax/Kling AI/ByteDance 等视频任务的轮询地址如finalPrediction.urls.get。AI SDK 会在服务端代为抓取这些 URL 并把结果交还给调用方。问题在于这个 URL 来自外部服务一个恶意或已被攻陷的 Provider或任何能篡改响应的人都可以把 URL 指向内网地址——云元数据端点http://169.254.169.254/…、私有主机http://10.0.0.5/…、甚至localhost。如果 SDK 不加校验地直接fetch就构成了典型的SSRF服务端请求伪造漏洞。AI SDK 的解决方案是把这个 URL 是否可信变成一个在每个调用点都必须做出的显式决策并在底层用一条完整的防护链路兜底。用户侧无需任何配置防护自动内置于 Provider 包中真正需要你关注的是当你自己编写 Provider 集成或注入自定义fetch时如何遵循这套规范。核心规则每次getFromApi都必须显式设置validateUrl当 Provider 用getFromApi抓取 URL 时必须显式传入validateUrl选项让每个调用点都留下可见的信任决策。该选项在类型上是可选的只是为了与外部调用ai-sdk/provider-utils的代码保持向后兼容省略它等价于false不校验因此本仓库内的 Provider 代码绝不可以在任何getFromApi调用中漏掉它。这条规则不是纸面约定而是被 CI 强制执行的。仓库内置的 oxlint 插件 tools/oxlint-plugin-ai-sdk/index.mjs 提供了ai-sdk/require-validate-url规则任何getFromApi调用若没有显式validateUrl属性且要求内联对象字面量若 options 是从别处构建的变量则直接判失败fail closedpnpm check就会失败。插件源码中明确写道omitting the flag skips URL validation, and that decision must be visible at the call site——省略即跳过校验而这个决策必须在调用点可见。决策模型validateUrl: true还是falsevalidateUrl: true—— 主机来自响应体数据当host或 host 之外的任何部分来自响应体时它属于攻击者可影响的输入必须走fetchWithValidatedRedirects这条受保护路径拒绝私有 / loopback / link-local 目标地址手动跟随并逐跳重新校验每一个重定向被拦截的 URL 抛出DownloadError。典型场景包括下载 URLjson.audio.url、image.url、轮询 URLfinalPrediction.urls.get以及需要鉴权的状态轮询——当初始 URL 由配置的 Provider 端点构建时把该端点作为trustedOrigin传入允许首个 hop但离开该源的所有重定向仍逐一校验。validateUrl: false—— URL 由开发者配置的端点构建当 URL 是从开发者配置的端点构建而来${config.baseURL}/…、config.url({ path })、${baseUrl.origin}/…最多只拼接一个路径段或 id且请求不需要经过校验的重定向路径时使用false。此时 host 由配置固定校验初始 URL 反而会破坏合法的自托管 / localhost baseURL。注意仅路径注入不构成 SSRF——host 无法被改变。判定口诀原文档如果 host、或 host 之外的任何部分来自响应体或者鉴权轮询必须校验重定向 → 用validateUrl: true。源码印证两个分支的实际行为get-from-api.ts 中两个分支的差异清晰可见const response validateUrl ? await fetchWithValidatedRedirects({ url, headers: requestHeaders, abortSignal, fetch, trustedOrigin, }) : await requestFetch(url, { method: GET, headers: requestHeaders, signal: abortSignal, });validateUrl: false直接走原始fetchvalidateUrl: true则进入受保护的fetchWithValidatedRedirects。同时注意getFromApi的参数注释packages/provider-utils/src/get-from-api.ts#L33-L65完整记录了三个选项的语义与安全约束是本规范的 API 级体现。fetchWithValidatedRedirects逐跳校验的重定向抓取受保护路径的核心实现位于 packages/provider-utils/src/fetch-with-validated-redirects.ts用redirect: manual手动跟随重定向这样每一跳都会在发出请求之前先通过validateDownloadUrl校验。如果依赖默认的redirect: follow请求会在我们看到重定向目标 URL 之前就发出去了防护形同虚设重定向状态码严格限定为 fetch 规范中的301/302/303/307/308300 Multiple Choices和304 Not Modified即使带Location头也不算重定向见 fetch-with-validated-redirects.ts#L12-L15最大重定向次数为 10MAX_DOWNLOAD_REDIRECTS 10超出即抛DownloadError每次重定向 hop 前会cancelResponseBody释放连接避免未消费的 3xx 响应体泄漏底层 socket在非浏览器运行时遇到opaqueredirect无法读取 Location 的 opaque 响应时直接 fail closed 抛错只有在真实浏览器中才允许原生跟随——因为浏览器受 CORS 约束无法触达服务端内网或云元数据而其他运行时无法校验该 hop。头部保护跨源重定向丢弃凭证服务端没有 CORS 预检保护所以仅按 fetch 规范剥离Authorization是不够的——Provider 的自定义鉴权头如x-key同样可能被带往陌生 host。因此 fetch-with-validated-redirects.ts#L132-L142 在跨源重定向时丢弃除User-Agent外的全部调用方 header而在首个请求发出前sanitizeRequestHeaders会剥离 proxy/metadata/cookie 等高风险头。自托管部署trustedOrigin豁免机制响应中的 URL 常常指回开发者自己配置的端点——API 主机上的轮询 URL、自托管服务器上的下载 URL。当该端点是私有的本地运行的 Replicate 兼容 cog server、内网 fal 部署时validateUrl: true会恰好拒绝开发者配置的那个 host。此时传入trustedOrigin配置的 base URL与该源同源的 hop 跳过目标校验其余每个 hop 仍然校验await getFromApi({ url: pollUrl, // from the response body validateUrl: true, trustedOrigin: this.config.baseURL, // … });为什么安全因为与配置端点同源的 URL恰好就是配置派生的validateUrl: false请求本来也会抓取的内容。实现上fetch-with-validated-redirects.ts#L99-L110 对 trusted hop 不仅跳过validateDownloadUrl还会直接使用globalThis.fetch而非经过 DNS 校验的默认下载 fetch——因为目标 host 是开发者信任的。硬性约束trustedOrigin必须是开发者配置值永远不能从响应数据中推导。凭证保护credentialedOrigin限定鉴权头发送范围当不受信任的 URL 在首跳上合法地需要携带 API key例如同 host 的轮询 URL时传入credentialedOrigin让 header仅当 URL 与该源同源时才发送await getFromApi({ url: pollUrl, // from the response body validateUrl: true, credentialedOrigin: this.config.baseURL, trustedOrigin: this.config.baseURL, headers: authHeaders, successfulResponseHandler, failedResponseHandler, fetch: this.config.fetch, });实现位于 get-from-api.ts#L70-L81当credentialedOrigin存在且 URL 与之不同源时outgoingHeaders被置为{}user-agent 后缀始终保留。也就是说凭证绝不会搭乘请求发往响应提供的异源 host如 CDN而后续跨源重定向也会按前述规则丢弃全部调用方 header形成双重保险。URL 校验器的具体规则拒绝什么、放行什么validateDownloadUrlpackages/provider-utils/src/validate-download-url.ts执行字符串 / 字面 IP 层面的检查协议仅允许http:、https:与data:data:是内联内容不触发网络请求无 SSRF 风险其余协议一律拒绝主机名拒绝localhost、.local、.localhost并剥离末尾点防止localhost.绕过块名单validate-download-url.ts#L37-L39IPv4 私有范围0/8、10/8、100.64/10CGNAT、127/8、169.254/16、172.16/12、192.0.0/24、192.0.2/24TEST-NET-1、192.168/16、198.18/15benchmark、198.51.100/24、203.0.113/24、224/4multicast及240/4reserved含广播地址validate-download-url.ts#L124-L156IPv6 私有范围::1loopback、::unspecified、fc00::/7ULA、fe80::/10link-local、fec0::/10site-local、ff00::/8multicast、2001:db8::/32、3fff::/20RFC 9637 文档段并解析内嵌 IPv4 的地址::ffff:127.0.0.1、64:ff9b::169.254.169.254等 NAT64 前缀复用 IPv4 私有检查validate-download-url.ts#L210-L268解析失败按 fail closed 处理视为不安全非法 IPv6 同样拒绝。被拦截的 URL 统一抛出DownloadErrorAI_DownloadError见 packages/provider-utils/src/download-error.ts携带url、statusCode、statusText信息。downloadBlob在 packages/provider-utils/src/download-blob.ts 中额外对响应体做 100 MiB 默认大小上限限制DEFAULT_MAX_DOWNLOAD_SIZE并释放非 2xx 响应的连接。DNS 校验与部署加固堵住 hostname 到内网 IP 的窗口字符串 / 字面 IP 检查无法覆盖域名解析后指向内网的情况更无法防止DNS rebinding校验时解析到公网 IP连接时再解析到内网 IP。因此Node.js 默认下载 fetch连接期校验 结果绑定在 Node.js 上默认的下载 fetchpackages/provider-utils/src/safe-node-fetch.ts通过undici的Agentconnect.lookup钩子在连接器内部完成 DNS 解析createSafeLookup强制以all: true解析全部 DNS 记录只要有一条地址属于私有/内部范围整个结果即被拒绝safe-node-fetch.ts#L63-L102校验通过后把这些确切的地址原样返回给连接器socket 被钉在已校验的结果上——DNS rebinding 无法在两次解析之间偷换地址仅当运行环境确认为 Node 且全局fetch仍是 Node 默认实现时才启用该保护getDefaultDownloadFetch有运行时探测与全局替换检测safe-node-fetch.ts#L122-L144。注入自定义fetch时的责任转移如果注入或全局替换了自定义fetch它必须提供等效的连接期校验见用户文档 content/docs/06-advanced/11-secure-url-fetching.mdx 中的完整示例用undici的Agent({ connect: { lookup: safeLookup } })包裹再通过createFal({ fetch: safeFetch })注入。SDK 的 URL 校验与自定义 fetch 的连接期钉扎是互补关系两者都应保留。其他运行时网络层限制出口其他服务端运行时无法使用 Node 的 DNS/socket 钩子应当在网络层限制出口egress禁止访问169.254.0.0/16、RFC-1918 内网段和 loopback——这是与代码无关的最稳健控制。仓库内实践验证fal Provider 的受保护调用规范在仓库内不是空谈。fal 包的四个模型均通过getFromApi抓取响应体 URL 并显式传入validateUrlfal-image-model.ts、fal-speech-model.ts、fal-transcription-model.ts、fal-video-model.ts配合 fetch-with-validated-redirects.test.ts 与 get-from-api.test.ts 中的测试用例可以验证私有/回环/链路本地目标被拒、跨源重定向丢弃凭证、trusted hop 豁免、重定向上限等行为。编写自己的 Provider 时请把这些测试当作行为契约。实战清单在 Provider 集成中遵循本规范每个getFromApi调用都必须显式写validateUrl让pnpm check通过ai-sdk/require-validate-url规则强制内联对象字面量URL 来自响应体 →validateUrl: true仅由配置端点拼接路径/ID →false自托管/本地端点响应 URL 指回配置端点时把配置的baseURL传给trustedOrigin仅限开发者配置值鉴权轮询首跳需要携带凭证时传credentialedOrigin为配置端点异源请求自动不带 header注入自定义fetch必须提供等效的连接期 DNS 校验与 socket 钉扎参考 11-secure-url-fetching.mdx 的undici示例非 Node 运行时在网络层限制出口被拦截的 URL 以DownloadError形式抛出调用方应将其视为可预期的安全异常并做相应处理。这套机制的完整用户视角说明位于 Secure URL Fetching贡献者视角的强制规范即本文主题文档 contributing/secure-url-handling.md 本身——两者互为表里共同定义了 AI SDK 服务端抓取 URL 的安全边界。【免费下载链接】aiThe AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents项目地址: https://gitcode.com/GitHub_Trending/ai/ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考