From 63c56da14346b28efc785488f9e08aa03c22151d Mon Sep 17 00:00:00 2001 From: robbond Date: Mon, 9 Nov 2026 15:56:30 +0000 Subject: [PATCH] fix(security): disable legacy XSS browser filter --- middleware.js | 2 +- next.config.js | 2 +- tests/security/security-headers.test.cjs | 100 +++++++++++++++++++++++ 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 tests/security/security-headers.test.cjs diff --git a/middleware.js b/middleware.js index 185b9205..77e18ca9 100644 --- a/middleware.js +++ b/middleware.js @@ -63,7 +63,7 @@ export function middleware(request) { ); // XSS protection (legacy) - response.headers.set("X-XSS-Protection", "1; mode=block"); + response.headers.set("X-XSS-Protection", "0"); // Prevent MIME type sniffing response.headers.set("X-Content-Type-Options", "nosniff"); diff --git a/next.config.js b/next.config.js index 2d7ee446..246c3f45 100644 --- a/next.config.js +++ b/next.config.js @@ -46,7 +46,7 @@ const securityHeaders = [ }, { key: "X-XSS-Protection", - value: "1; mode=block" + value: "0" } // { // key: "Content-Security-Policy", diff --git a/tests/security/security-headers.test.cjs b/tests/security/security-headers.test.cjs new file mode 100644 index 00000000..160bbeaa --- /dev/null +++ b/tests/security/security-headers.test.cjs @@ -0,0 +1,100 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); + +const rootDirectory = path.join(__dirname, "..", ".."); + +const toHeaderObject = (headers) => + Object.fromEntries(headers.map(({ key, value }) => [key, value])); + +const loadMiddleware = () => { + const middlewarePath = path.join(rootDirectory, "middleware.js"); + const source = fs + .readFileSync(middlewarePath, "utf8") + .replace( + 'import { NextResponse } from "next/server";', + 'const { NextResponse } = require("next/server");' + ) + .replace("export function middleware", "function middleware") + .concat("\nmodule.exports = { middleware };\n"); + const context = { + Buffer, + Headers, + module: { exports: {} }, + process: { + env: { + NEXTAUTH_URL: "https://example.test", + NODE_ENV: "production" + } + }, + require: (moduleName) => { + assert.strictEqual(moduleName, "next/server"); + return { + NextResponse: { + next: () => ({ headers: new Headers() }) + } + }; + }, + crypto: { + randomUUID: () => "00000000-0000-4000-8000-000000000000" + } + }; + + vm.runInNewContext(source, context, { filename: middlewarePath }); + return context.module.exports.middleware; +}; + +const run = async () => { + const nextConfig = require(path.join(rootDirectory, "next.config.js")); + const configuredRules = await nextConfig.headers(); + const globalRule = configuredRules.find( + ({ source }) => source === "/(.*)?" + ); + + assert.ok(globalRule, "Expected the global Next.js security-header rule"); + assert.deepStrictEqual(toHeaderObject(globalRule.headers), { + "X-DNS-Prefetch-Control": "on", + "X-Frame-Options": "SAMEORIGIN", + "X-Content-Type-Options": "nosniff", + "X-Permitted-Cross-Domain-Policies": "none", + "Referrer-Policy": "origin-when-cross-origin", + "Strict-Transport-Security": + "max-age=63072000; includeSubDomains; preload", + "X-XSS-Protection": "0" + }); + + const middleware = loadMiddleware(); + const response = middleware({ headers: new Headers() }); + + assert.strictEqual(response.headers.get("X-XSS-Protection"), "0"); + assert.strictEqual(response.headers.get("X-Content-Type-Options"), "nosniff"); + assert.strictEqual(response.headers.get("X-Frame-Options"), "DENY"); + assert.strictEqual( + response.headers.get("Referrer-Policy"), + "strict-origin-when-cross-origin" + ); + assert.strictEqual( + response.headers.get("Permissions-Policy"), + "geolocation=(), camera=(), microphone=(), fullscreen=(self)" + ); + assert.strictEqual( + response.headers.get("Strict-Transport-Security"), + "max-age=63072000; includeSubDomains; preload" + ); + assert.match( + response.headers.get("Content-Security-Policy"), + /default-src 'self'/ + ); + + console.log("Security-header regression tests passed (2/2)."); +}; + +if (require.main === module) { + run().catch((error) => { + console.error(error); + process.exit(1); + }); +} + +module.exports = run;