Next.js remotePatterns: there is no safe "allow all"
There is no safe "allow all" for next/image: a remotePatterns hostname of '*' matches every host, the same as the double-star wildcard, so anyone can make /_next/image fetch and re-encode images from any URL at your expense. Pin each exact host and path instead (for Supabase, your own project ref and bucket), and render truly arbitrary URLs with unoptimized.
You render a Supabase Storage URL with <Image> and next dev throws:
Invalid src prop (https://abcd1234.supabase.co/storage/v1/object/public/avatars/u1.png) on `next/image`, hostname "abcd1234.supabase.co" is not configured under images in your `next.config.js`
See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host
Or an old config still uses images.domains, and Next warns:
`images.domains` is deprecated in favor of `images.remotePatterns`. Please update next.config.ts to protect your application from malicious users.
Both are verbatim from the Next.js 16.4.3 source. Search "allow all" from either and a top-ranking answer still says hostname: '*'. Next accepts it, and your optimizer then fetches from any host.
Is there a way to allow all domains in next/image?
Not a safe one. The only "allow all" is a wildcard hostname in images.remotePatterns, and it turns /_next/image into a public image fetcher: anyone can request /_next/image?url=<any image URL>&w=640&q=75, and your deployment downloads, decodes, resizes and caches it.
images.domains: ['*'] doesn't even do that. Next compares each domains entry to the hostname with ===, so '*' only matches a host literally named *.
Is hostname '*' safer than '**'?
No. The docs say * matches a single subdomain, but Next tests the hostname with picomatch.makeRe(pattern.hostname), and picomatch's * stops only at path separators. Hostnames have no slashes, so a lone * spans every dot. We ran Next's own matchRemotePattern from next@16.4.2:
hostname | evil.com | a.b.c.evil.com | attacker.supabase.co | a.b.example.com |
|---|---|---|---|---|
'**' | match | match | match | match |
'*' | match | match | match | match |
'*.*' | match | match | match | match |
'*.supabase.co' | no | no | match | no |
'*.example.com' | no | no | no | match |
Even the last row breaks the "single subdomain" reading: *.example.com accepts a.b.example.com. A bare '*' or '**' also accepts localhost and 169.254.169.254; Next.js 16 still refuses private IPs at fetch time unless dangerouslyAllowLocalIP is set, which is its own "url" parameter is not allowed story.
Here's that config, with the comment that usually comes with it:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
// Fixes: hostname "abcd1234.supabase.co" is not configured under images.
// One star is one subdomain level, so this can't match everything.
{ protocol: "https", hostname: "*" },
],
},
};
export default nextConfig;
- Warningnext.config.ts:8
Image optimizer allows any remote host
List the exact hosts you serve images from (e.g. { protocol: 'https', hostname: '<ref>.supabase.co', pathname: '/storage/v1/object/public/**' }). A single '*' is not narrower than '**': in Next.js both match every hostname when used alone, and '*.example.com' also matches deeper names such as a.b.example.com. A scoped wildcard is only as safe as the domain under it: '**.example.com' is reasonable when you control every subdomain of example.com, but on a shared multi-tenant domain such as '*.supabase.co' or '*.vercel.app' it allow-lists every other customer's project too, so pin your own project host instead. If you truly must render arbitrary user-supplied image URLs, set unoptimized on those <Image>s instead of opening the optimizer to the whole internet.
What can anyone do with an allow-all pattern?
Point your optimizer at any image, as often as they like. That costs you three ways:
- Your quota. Vercel checks
remotePatterns, then bills an image transformation on every cache MISS, and the sourceurlis part of the cache key, so every new URL is a miss. Hobby includes 5K transformations a month; past that, new images fail to optimize with a 402, yours included. Pro bills them at the on-demand rate. - Your server, when self-hosting. Explaining in 2020 why the host check can't be switched off (vercel/next.js#18632), Tim Neutkens listed CPU spent on other sites' images, a cache that grows without limit when self-hosting, and attacker images served from your domain for social engineering.
- Advisory preconditions. CVE-2025-59471 (Moderate DoS in self-hosted apps, fixed in 15.5.10 and 16.1.5) needs an attacker who can serve a large image on an allowed domain. CVE-2026-94483 (High, SSRF during Image Optimization, 16.x before 16.3.8) needs an attacker-controlled, allow-listed URL. With a wildcard, every domain the attacker owns qualifies.
GitHub's advisory for CVE-2026-94483 (GHSA-cjq9-62q9-8jv4, published September 30, 2026) gives one workaround: audit
images.remotePatternsfor hosts "that may not be trusted with their DNS entries." A wildcard trusts every DNS entry on the internet. Formbricks removed itshostname: "**"in PR #8501, merged July 23, 2026: the optimizer now serves a curated list of provider hosts, and every other external image renders unoptimized.
Patch either way. 16.4.1 and 15.5.28 fix the October 14, 2026 critical RCEs, one of them in the Image Optimization API (CVE-2026-96889), so treat a lower next like any other vulnerable dependency. For the image RCE, the advisory's workaround is sharp 0.35.5 or later, and next 15.5.28 still accepts a sharp 0.34.x already in your lockfile, so confirm npm ls sharp shows 0.35.5 or later.
Why isn't *.supabase.co a safe middle ground?
Because it's a shared tenant domain. Every Supabase project is served from <project_ref>.supabase.co, and public bucket objects live at /storage/v1/object/public/<bucket>/<path>, readable by anyone with the URL. Anyone can create a free project, upload an image, and point your optimizer at it. *.supabase.co allow-lists every other customer's storage, and the CVE-2025-59471 attacker is now anyone with a Supabase account.
The Next.js docs warn that most remote image servers are shared between tenants, and Vercel's docs suggest putting your account id in pathname when you don't own the host. The same goes for *.vercel.app and *.s3.amazonaws.com.
How do I allow Supabase Storage images in next/image safely?
Pin your project host and the bucket path, and refuse query strings:
import type { NextConfig } from "next";
// abcd1234.supabase.co, never *.supabase.co
const supabaseHost = new URL(process.env.NEXT_PUBLIC_SUPABASE_URL!).hostname;
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: supabaseHost,
port: "",
pathname: "/storage/v1/object/public/avatars/**",
search: "",
},
],
},
};
export default nextConfig;
- Derive the host from the URL you already use. If the variable is missing,
new URLthrows and the build fails loudly, which beats a silent wildcard. - Pin the bucket, so your other public buckets don't become optimizer input.
search: ""rejects query strings; pinsearch: "?v=2"instead if you use a fixed cache-buster. - Migrating from
domains: each entry was an exact host with any protocol, port and path. Write one pattern per host with protocol and pathname pinned. - Truly arbitrary URLs (user-supplied avatar links, link previews): set
unoptimizedon those<Image>s and keep the wildcard out ofremotePatterns. The browser loads the original and your server fetches nothing. - Or let Supabase resize. Its documented Next.js loader (
loader: 'custom'plus aloaderFilethat returns/storage/v1/render/image/public/...URLs) bypasses the Next optimizer. Image transformations need the Pro plan or above.
What GuardLayer flags, and what it doesn't
nextjs/image-remote-wildcard-host (warning) reads next.config.{js,cjs,mjs,ts} and flags a remotePatterns hostname of exactly '*' or '**', the dotted catch-alls ('*.*', '**.**' and the mixed forms), and new URL('https://**/...'). It skips commented-out entries, and configs with a literal unoptimized: true or loader: 'custom', since then the optimizer fetches nothing.
It does not flag *.supabase.co, *.vercel.app or other scoped wildcards: it can't know whether you own the domain under the star, so that review is yours. It doesn't resolve variable hostnames and never checks pathname.
Quick self-check
grep -nE "hostname['\"]?[[:space:]]*:[[:space:]]*['\"\`]\*|https?://\*|\*\.(supabase\.co|vercel\.app)" next.config.*
Then ask production to fetch an image from a host you never configured:
curl -s -o /dev/null -w "%{http_code}\n" \
"https://your-app.example/_next/image?url=https%3A%2F%2Fother-host.example%2Fphoto.png&w=640&q=75"
Use any real PNG on an unlisted host (640 and 75 are valid under the v16 defaults). A 200 means your optimizer fetched it. A 400 means it refused (self-hosted Next answers "url" parameter is not allowed).
FAQ
Does images.domains: ['*'] allow all domains?
No. Next matches domains entries by exact string equality, so '*' only matches a host literally named *. domains has been deprecated since Next.js 14; move to pinned remotePatterns.
Is hostname '*' narrower than '**' in remotePatterns?
No. Next tests hostnames with picomatch, which doesn't treat dots as separators, so a bare * matches every hostname, exactly like the double star. We verified it on next 16.4.2.
Is a *.supabase.co wildcard safe for Supabase Storage images?
No. Every Supabase project is a subdomain of supabase.co, so the pattern allow-lists every other project's public files. Pin your own <ref>.supabase.co and the bucket path.
Does unoptimized: true make an allow-all config safe?
A global images.unoptimized: true does: self-hosted Next then answers /_next/image with a 404, and GuardLayer skips the rule. The per-image unoptimized prop doesn't, because the endpoint still accepts any URL your patterns allow.
Does an allow-all pattern matter on Vercel?
Yes. Vercel checks remotePatterns before optimizing and bills a transformation on every cache miss, so a wildcard lets anyone spend your quota. CVE-2025-59471 targets self-hosted apps; on Vercel the abuse shows up as usage.
Catch this before it ships — free
Scan your files for this and 35 other Next.js + Supabase issues, with the exact fix inline. Connect GitHub and GuardLayer checks every push too — free on public repos.
No signup, no card. Raw files aren’t kept; only the report is saved.