Skip to content

SSRF URL validation in @ai-sdk/provider-utils@4.0.19 rejects data: URIs passed as inline images #13354

@dylanmoz

Description

@dylanmoz

Description

Summary

@ai-sdk/provider-utils@4.0.19 introduced SSRF protection (validateDownloadUrl) in PR #13085 (commit ad4cfc2) that enforces an http:/https:-only protocol allowlist on all URLs passed through downloadBlob() and download(). This rejects data: URIs, which are a legitimate and commonly-used way to pass inline images to the SDK.

Reproduction

import { generateText } from "ai";

const image = "..."; // base64-encoded PNG

await generateText({
  model: yourModel,
  messages: [
    {
      role: "user",
      content: [
        {
          type: "image",
          image: `data:image/png;base64,${image}`,
        },
        { type: "text", text: "Describe this image" },
      ],
    },
  ],
});

This throws:

DownloadError: URL scheme must be http or https, got data:

Expected behavior

data: URIs should be accepted as valid inline image sources. They are not fetched over the network and pose no SSRF risk — the data is already embedded in the URI itself.

Root cause

validateDownloadUrl() in packages/provider-utils/src/validate-download-url.ts uses a strict protocol allowlist (lines 22-27):

if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
  throw new DownloadError({
    url,
    message: `URL scheme must be http or https, got ${parsed.protocol}`,
  });
}

This is applied unconditionally in downloadBlob() and download() before any protocol-specific handling. The data: protocol is safe — it encodes content inline and makes no network request — but is caught by the blanket rejection.

Suggested fix

The validation should either:

  1. Allowlist data: alongside http:/https:data: URIs are parsed in-process by fetch() and never leave the machine, so they carry no SSRF risk.
  2. Skip validation entirely for data: URIs — check for data: before calling validateDownloadUrl() and handle them through a non-network code path.
  3. Make the allowlist configurable — the PR's own "Future Work" section acknowledges this: "Consider making the blocklist configurable for users who need to access private networks intentionally."

Option 1 is the simplest fix. The change in validate-download-url.ts would be:

if (
  parsed.protocol !== "http:" &&
  parsed.protocol !== "https:" &&
  parsed.protocol !== "data:"
) {
  throw new DownloadError({
    url,
    message: `URL scheme must be http or https, got ${parsed.protocol}`,
  });
}

AI SDK Version

  • @ai-sdk/provider-utils: 4.0.19 (broken) — works in 4.0.18

AI SDK Version

  • @ai-sdk/provider-utils: 4.0.19

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    ai/corecore functions like generateText, streamText, etc. Provider utils, and provider spec.ai/providerrelated to a provider package. Must be assigned together with at least one `provider/*` labelbugSomething isn't working as documentedreproduction providedsupport

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions