fix: send HSTS on TLS requests and drop the X-Powered-By header

This commit is contained in:
Linus Rath
2026-10-19 01:32:27 +02:00
parent 6275533613
commit 1fd5d0558c
3 changed files with 22 additions and 0 deletions
@@ -135,6 +135,19 @@ describe('ALLOWED_FRAME_ANCESTORS', () => {
else process.env.ALLOWED_FRAME_ANCESTORS = original;
});
it('sends HSTS only on a request that arrived over TLS', async () => {
const plain = await load('/en/mail');
expect(plain.headers.get('strict-transport-security')).toBeNull();
const forwarded = await proxy(new NextRequest('http://localhost:3000/en/mail', {
headers: { 'sec-fetch-dest': 'document', 'sec-fetch-mode': 'navigate', 'sec-fetch-site': 'none', 'x-forwarded-proto': 'https' },
}));
expect(forwarded.headers.get('strict-transport-security')).toBe('max-age=63072000');
const direct = await proxy(new NextRequest('https://webmail.example/en/mail', {
headers: { 'sec-fetch-dest': 'document', 'sec-fetch-mode': 'navigate', 'sec-fetch-site': 'none' },
}));
expect(direct.headers.get('strict-transport-security')).toBe('max-age=63072000');
});
it('lets a portal frame the mail UI', async () => {
const res = await load('/en/mail');
expect(res.headers.get('content-security-policy')).toContain('frame-ancestors https://portal.example');
+2
View File
@@ -49,6 +49,8 @@ if (isLite && (process.env.NEXT_PUBLIC_LOCALE_PREFIX ?? "never") !== "always") {
const nextConfig: NextConfig = {
output: isLite ? "export" : "standalone",
// No `X-Powered-By: Next.js` on every response.
poweredByHeader: false,
// Directory-style pages (out/en/mail/index.html) are what static hosts and
// the shipped SPA-fallback rules expect; next/image has no optimizer here.
...(isLite ? { trailingSlash: true, images: { unoptimized: true } } : {}),
+7
View File
@@ -398,6 +398,13 @@ export async function proxy(request: NextRequest) {
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
response.headers.set("X-XSS-Protection", "0");
// Only on a request that arrived over TLS (directly or via the reverse
// proxy's X-Forwarded-Proto): a plain-http dev or LAN install must not be
// pinned to https by a header it cannot honour. No includeSubDomains, which
// would speak for the operator's other hosts.
if (request.nextUrl.protocol === "https:" || request.headers.get("x-forwarded-proto") === "https") {
response.headers.set("Strict-Transport-Security", "max-age=63072000");
}
response.headers.set(
"Permissions-Policy",
"camera=(), microphone=(), geolocation=(), payment=()"