diff --git a/packages/next/src/server/lib/router-utils/block-cross-site.ts b/packages/next/src/server/lib/router-utils/block-cross-site.ts index f4bd72b56d2be..0ea728fef158f 100644 --- a/packages/next/src/server/lib/router-utils/block-cross-site.ts +++ b/packages/next/src/server/lib/router-utils/block-cross-site.ts @@ -31,6 +31,25 @@ function warnOrBlockRequest( return true } +function isInternalDevEndpoint(req: IncomingMessage): boolean { + if (!req.url) return false + + try { + // TODO: We should standardize on a single prefix for this + const isMiddlewareRequest = req.url.includes('/__nextjs') + const isInternalAsset = req.url.includes('/_next') + // Static media requests are excluded, as they might be loaded via CSS and would fail + // CORS checks. + const isIgnoredRequest = + req.url.includes('/_next/image') || + req.url.includes('/_next/static/media') + + return !isIgnoredRequest && (isInternalAsset || isMiddlewareRequest) + } catch (err) { + return false + } +} + export const blockCrossSite = ( req: IncomingMessage, res: ServerResponse | Duplex, @@ -51,8 +70,7 @@ export const blockCrossSite = ( } // only process internal URLs/middleware - // TODO: We should standardize on a single prefix for this - if (!req.url?.includes('/_next') && !req.url?.includes('/__nextjs')) { + if (!isInternalDevEndpoint(req)) { return false } // block non-cors request from cross-site e.g. script tag on diff --git a/test/development/basic/allowed-dev-origins.test.ts b/test/development/basic/allowed-dev-origins.test.ts index b0b25aa20c03a..81dcce643296e 100644 --- a/test/development/basic/allowed-dev-origins.test.ts +++ b/test/development/basic/allowed-dev-origins.test.ts @@ -318,6 +318,37 @@ describe.each([['', '/docs']])( server.close() } }) + + it('should load images regardless of allowed origins', async () => { + const { server, port } = await createHostServer() + try { + const browser = await webdriver(`http://127.0.0.1:${port}`, '/about') + + const imageSnippet = `(() => { + const statusEl = document.createElement('p') + statusEl.id = 'status' + document.querySelector('body').appendChild(statusEl) + + const image = document.createElement('img') + image.src = "${next.url}/_next/image?url=%2Fimage.png&w=256&q=75" + document.querySelector('body').appendChild(image) + image.onload = () => { + statusEl.innerText = 'OK' + } + image.onerror = () => { + statusEl.innerText = 'Unauthorized' + } + })()` + + await browser.eval(imageSnippet) + + await retry(async () => { + expect(await browser.elementByCss('#status').text()).toBe('OK') + }) + } finally { + server.close() + } + }) }) } ) diff --git a/test/development/basic/misc/public/image.png b/test/development/basic/misc/public/image.png new file mode 100644 index 0000000000000..7cbc1d2673361 Binary files /dev/null and b/test/development/basic/misc/public/image.png differ