Skip to content

Image loader does not validate redirect targets: remotePatterns and the scheme check apply to the first URL only #1337

Description

@arpitjain099

What happens

/_next/image validates the url query parameter, then follows redirects without re-checking where they lead.

In packages/cloudflare/src/cli/templates/images.ts, validateUrlQueryParameter checks the scheme and calls hasRemoteMatch(__IMAGES_REMOTE_PATTERNS__, parsedURL). Both run once, against the original URL. fetchWithRedirects then reads the Location header and recurses into itself with the new target, and hasRemoteMatch is not called again. Grepping the file, hasRemoteMatch appears in exactly two places: its definition and that single call site.

So from hop one onwards there is no scheme check and no allow-list check. An allowlisted host that returns a redirect decides where the loader goes next, including to a literal loopback, link-local or RFC1918 address.

Reproduced by reading the code at @opennextjs/cloudflare@1.20.2 and on main; the shape is unchanged in both.

Why not simply re-run the allow list

Re-running hasRemoteMatch on every hop would break a legitimate and common pattern: an allowlisted CDN redirecting to a signed URL on a sibling domain that is not itself in remotePatterns. That seemed likely to break real deployments.

Next's own optimizer does not re-apply remotePatterns per hop either. What it does do is re-enter the whole of fetchExternalImage on each hop, so its private-address guard runs again every time. That guard uses Node's DNS module, which has no equivalent in the Workers runtime, so it cannot be ported directly.

Suggested shape

Check each redirect target for scheme and for a literal non-routable address before following it. That is coarse, since without DNS a hostname cannot be resolved to find out where it points, but it closes the unambiguous cases and needs no new configuration.

There is already a // TODO: Add dangerouslyAllowLocalIP support in fetchWithRedirects, so this is adjacent to something the code already anticipates.

PR: #1338

Note on scope

This was originally raised privately. Cloudflare's security team reviewed it and concluded it does not demonstrate a security-boundary violation, so it is filed here as a hardening item at their suggestion. Worth stating plainly so nobody reads more into it than that: on a standard deployment fetch() goes to the public internet, private network access requires separate bindings, and the response still has to pass image content-type detection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions