Skip to content

Commit d9bcb83

Browse files
ztannerijjk
authored andcommitted
ensure /__next middleware URLs are included in the origin check (#77416)
We have special development endpoints that are also prefixed under `/__nextjs`. This updates the origin checking logic to account for those in addition to `/_next`, and adds a test.
1 parent cfeaa86 commit d9bcb83

File tree

2 files changed

+106
-59
lines changed

2 files changed

+106
-59
lines changed

packages/next/src/server/lib/router-utils/block-cross-site.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ export const blockCrossSite = (
5050
allowedOrigins.push(hostname)
5151
}
5252

53-
// only process _next URLs
54-
if (!req.url?.includes('/_next')) {
53+
// only process internal URLs/middleware
54+
// TODO: We should standardize on a single prefix for this
55+
if (!req.url?.includes('/_next') && !req.url?.includes('/__nextjs')) {
5556
return false
5657
}
5758
// block non-cors request from cross-site e.g. script tag on

test/development/basic/allowed-dev-origins.test.ts

+103-57
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@ import { createNext, FileRef } from 'e2e-utils'
55
import { NextInstance } from 'e2e-utils'
66
import { fetchViaHTTP, findPort, retry } from 'next-test-utils'
77

8+
async function createHostServer() {
9+
const server = http.createServer((req, res) => {
10+
res.end(`
11+
<html>
12+
<head>
13+
<title>testing cross-site</title>
14+
</head>
15+
<body></body>
16+
</html>
17+
`)
18+
})
19+
20+
const port = await findPort()
21+
await new Promise<void>((res) => {
22+
server.listen(port, () => res())
23+
})
24+
25+
return {
26+
server,
27+
port,
28+
}
29+
}
30+
831
describe.each([['', '/docs']])(
932
'allowed-dev-origins, basePath: %p',
1033
(basePath: string) => {
@@ -34,21 +57,8 @@ describe.each([['', '/docs']])(
3457
afterAll(() => next.destroy())
3558

3659
it('should warn about WebSocket from cross-site', async () => {
37-
let server = http.createServer((req, res) => {
38-
res.end(`
39-
<html>
40-
<head>
41-
<title>testing cross-site</title>
42-
</head>
43-
<body></body>
44-
</html>
45-
`)
46-
})
60+
const { server, port } = await createHostServer()
4761
try {
48-
const port = await findPort()
49-
await new Promise<void>((res) => {
50-
server.listen(port, () => res())
51-
})
5262
const websocketSnippet = `(() => {
5363
const statusEl = document.createElement('p')
5464
statusEl.id = 'status'
@@ -88,22 +98,10 @@ describe.each([['', '/docs']])(
8898
}
8999
})
90100

91-
it('should not allow loading scripts from cross-site', async () => {
92-
let server = http.createServer((req, res) => {
93-
res.end(`
94-
<html>
95-
<head>
96-
<title>testing cross-site</title>
97-
</head>
98-
<body></body>
99-
</html>
100-
`)
101-
})
101+
it('should warn about loading scripts from cross-site', async () => {
102+
const { server, port } = await createHostServer()
103+
102104
try {
103-
const port = await findPort()
104-
await new Promise<void>((res) => {
105-
server.listen(port, () => res())
106-
})
107105
const scriptSnippet = `(() => {
108106
const statusEl = document.createElement('p')
109107
statusEl.id = 'status'
@@ -146,6 +144,46 @@ describe.each([['', '/docs']])(
146144
server.close()
147145
}
148146
})
147+
148+
it('should warn about loading internal middleware from cross-site', async () => {
149+
const { server, port } = await createHostServer()
150+
try {
151+
const browser = await webdriver(`http://127.0.0.1:${port}`, '/about')
152+
153+
const middlewareSnippet = `(() => {
154+
const statusEl = document.createElement('p')
155+
statusEl.id = 'status'
156+
document.querySelector('body').appendChild(statusEl)
157+
158+
const xhr = new XMLHttpRequest()
159+
xhr.open('GET', '${next.url}/__nextjs_error_feedback?errorCode=0&wasHelpful=true', true)
160+
xhr.send()
161+
162+
xhr.onload = () => {
163+
statusEl.innerText = "OK"
164+
}
165+
xhr.onerror = () => {
166+
statusEl.innerText = "Unauthorized"
167+
}
168+
})()`
169+
170+
await browser.eval(middlewareSnippet)
171+
172+
await retry(async () => {
173+
// TODO: These requests seem to be blocked regardless of our handling only when running with Turbopack
174+
// Investigate why this is the case
175+
if (!process.env.TURBOPACK) {
176+
expect(await browser.elementByCss('#status').text()).toBe('OK')
177+
}
178+
179+
expect(next.cliOutput).toContain(
180+
'Cross origin request detected from'
181+
)
182+
})
183+
} finally {
184+
server.close()
185+
}
186+
})
149187
})
150188

151189
describe('block mode', () => {
@@ -173,21 +211,8 @@ describe.each([['', '/docs']])(
173211
afterAll(() => next.destroy())
174212

175213
it('should not allow dev WebSocket from cross-site', async () => {
176-
let server = http.createServer((req, res) => {
177-
res.end(`
178-
<html>
179-
<head>
180-
<title>testing cross-site</title>
181-
</head>
182-
<body></body>
183-
</html>
184-
`)
185-
})
214+
const { server, port } = await createHostServer()
186215
try {
187-
const port = await findPort()
188-
await new Promise<void>((res) => {
189-
server.listen(port, () => res())
190-
})
191216
const websocketSnippet = `(() => {
192217
const statusEl = document.createElement('p')
193218
statusEl.id = 'status'
@@ -222,21 +247,8 @@ describe.each([['', '/docs']])(
222247
})
223248

224249
it('should not allow loading scripts from cross-site', async () => {
225-
let server = http.createServer((req, res) => {
226-
res.end(`
227-
<html>
228-
<head>
229-
<title>testing cross-site</title>
230-
</head>
231-
<body></body>
232-
</html>
233-
`)
234-
})
250+
const { server, port } = await createHostServer()
235251
try {
236-
const port = await findPort()
237-
await new Promise<void>((res) => {
238-
server.listen(port, () => res())
239-
})
240252
const scriptSnippet = `(() => {
241253
const statusEl = document.createElement('p')
242254
statusEl.id = 'status'
@@ -272,6 +284,40 @@ describe.each([['', '/docs']])(
272284
server.close()
273285
}
274286
})
287+
288+
it('should not allow loading internal middleware from cross-site', async () => {
289+
const { server, port } = await createHostServer()
290+
try {
291+
const browser = await webdriver(`http://127.0.0.1:${port}`, '/about')
292+
293+
const middlewareSnippet = `(() => {
294+
const statusEl = document.createElement('p')
295+
statusEl.id = 'status'
296+
document.querySelector('body').appendChild(statusEl)
297+
298+
const xhr = new XMLHttpRequest()
299+
xhr.open('GET', '${next.url}/__nextjs_error_feedback?errorCode=0&wasHelpful=true', true)
300+
xhr.send()
301+
302+
xhr.onload = () => {
303+
statusEl.innerText = "OK"
304+
}
305+
xhr.onerror = () => {
306+
statusEl.innerText = "Unauthorized"
307+
}
308+
})()`
309+
310+
await browser.eval(middlewareSnippet)
311+
312+
await retry(async () => {
313+
expect(await browser.elementByCss('#status').text()).toBe(
314+
'Unauthorized'
315+
)
316+
})
317+
} finally {
318+
server.close()
319+
}
320+
})
275321
})
276322
}
277323
)

0 commit comments

Comments
 (0)