Skip to content

Commit 0f9a390

Browse files
fix(security): skip allowedDomains header check for prerendered routes
1 parent 360fa3f commit 0f9a390

3 files changed

Lines changed: 86 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Fixes a spurious `Astro.request.headers` warning on prerendered pages when `security.allowedDomains` is configured. The internal `allowedDomains` header validation now skips prerendered routes, since they use synthetic requests with no real headers.

packages/astro/src/core/fetch/fetch-state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ export class FetchState implements AstroFetchState {
306306
// allowedDomains — without it, forwarded headers are never trusted
307307
// and the validation is a no-op. This avoids header lookups on the
308308
// hot path for the vast majority of apps.
309-
if (pipeline.manifest.allowedDomains && pipeline.manifest.allowedDomains.length > 0) {
309+
if (pipeline.manifest.allowedDomains && pipeline.manifest.allowedDomains.length > 0 && !this.routeData?.prerender) {
310310
this.#applyForwardedHeaders();
311311
}
312312

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import assert from 'node:assert/strict';
2+
import { describe, it } from 'node:test';
3+
import { FetchState } from '../../../dist/core/fetch/fetch-state.js';
4+
import { createRouteData } from '../mocks.ts';
5+
import { createBasicPipeline, SpyLogger } from '../test-utils.ts';
6+
7+
describe('FetchState with allowedDomains and prerendered routes', () => {
8+
it('does not access request.headers for prerendered routes', () => {
9+
const spy = new SpyLogger();
10+
const pipeline = createBasicPipeline({
11+
logger: spy,
12+
manifest: {
13+
allowedDomains: [{ hostname: 'example.com' }],
14+
},
15+
});
16+
17+
const routeData = createRouteData({
18+
route: '/',
19+
prerender: true,
20+
});
21+
22+
// Set up a request with a headers trap (mimics createRequest with isPrerendered: true)
23+
const request = new Request('http://localhost:4321/');
24+
let headersAccessed = false;
25+
const originalHeaders = request.headers;
26+
Object.defineProperty(request, 'headers', {
27+
get() {
28+
headersAccessed = true;
29+
return originalHeaders;
30+
},
31+
});
32+
33+
new FetchState(pipeline, request, {
34+
routeData,
35+
addCookieHeader: false,
36+
clientAddress: undefined,
37+
locals: undefined,
38+
prerenderedErrorPageFetch: fetch,
39+
waitUntil: undefined,
40+
});
41+
42+
assert.equal(headersAccessed, false, 'request.headers should not be accessed for prerendered routes');
43+
});
44+
45+
it('accesses request.headers for non-prerendered routes when allowedDomains is set', () => {
46+
const spy = new SpyLogger();
47+
const pipeline = createBasicPipeline({
48+
logger: spy,
49+
manifest: {
50+
allowedDomains: [{ hostname: 'example.com' }],
51+
},
52+
});
53+
54+
const routeData = createRouteData({
55+
route: '/',
56+
prerender: false,
57+
});
58+
59+
const request = new Request('http://localhost:4321/');
60+
let headersAccessed = false;
61+
const originalHeaders = request.headers;
62+
Object.defineProperty(request, 'headers', {
63+
get() {
64+
headersAccessed = true;
65+
return originalHeaders;
66+
},
67+
});
68+
69+
new FetchState(pipeline, request, {
70+
routeData,
71+
addCookieHeader: false,
72+
clientAddress: undefined,
73+
locals: undefined,
74+
prerenderedErrorPageFetch: fetch,
75+
waitUntil: undefined,
76+
});
77+
78+
assert.equal(headersAccessed, true, 'request.headers should be accessed for non-prerendered routes');
79+
});
80+
});

0 commit comments

Comments
 (0)