diff --git a/changelog.d/security-370-powered-by-header.md b/changelog.d/security-370-powered-by-header.md new file mode 100644 index 0000000..77b35f7 --- /dev/null +++ b/changelog.d/security-370-powered-by-header.md @@ -0,0 +1,2 @@ +- The docs site no longer sends `x-powered-by: Next.js` on responses + (`poweredByHeader: false` in `next.config.ts`, hivecommons/docs#370). diff --git a/next.config.ts b/next.config.ts index 516cd85..545c4dc 100644 --- a/next.config.ts +++ b/next.config.ts @@ -18,6 +18,8 @@ const nextConfig: NextConfig = { optimizePackageImports: ["@/components"], }, pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"], + // Do not advertise the framework on every response (x-powered-by). + poweredByHeader: false, async headers() { // Content-Security-Policy for the docs site. // - default-src 'self': blocks all unlisted resource types by default. diff --git a/src/__tests__/next-config-headers.test.ts b/src/__tests__/next-config-headers.test.ts index 950eb9c..860090c 100644 --- a/src/__tests__/next-config-headers.test.ts +++ b/src/__tests__/next-config-headers.test.ts @@ -29,6 +29,7 @@ let headerRules: HeaderRule[]; let rewrites: Rewrite[]; let redirects: Redirect[]; let pageExtensions: string[]; +let poweredByHeader: boolean | undefined; let siteHeaders: Map; let csp: Map; @@ -38,6 +39,7 @@ beforeAll(async () => { rewrites = (await config.rewrites!()) as Rewrite[]; redirects = (await config.redirects!()) as Redirect[]; pageExtensions = config.pageExtensions!; + poweredByHeader = config.poweredByHeader; const siteWide = headerRules.find(r => r.source === "/(.*)"); expect(siteWide, "a site-wide header rule for /(.*)").toBeDefined(); @@ -74,6 +76,12 @@ describe("next.config.ts site-wide security headers", () => { } }); + it("does not advertise the framework via x-powered-by", () => { + // Next.js defaults poweredByHeader to true; the flag must be set + // explicitly to false, not merely left undefined. + expect(poweredByHeader).toBe(false); + }); + it("sends HSTS for at least one year with includeSubDomains and preload", () => { const hsts = siteHeaders.get("Strict-Transport-Security")!; const maxAge = Number(/max-age=(\d+)/.exec(hsts)?.[1]);