From 89527fdb24dfef00068b90f226d11ea1a0f594d4 Mon Sep 17 00:00:00 2001 From: sec-check <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Sun, 18 Oct 2026 21:31:40 -0400 Subject: [PATCH] fix: stop sending x-powered-by on docs responses Set poweredByHeader: false in next.config.ts so responses no longer advertise the framework, and guard the flag in the next.config header test so it cannot silently regress. Closes #370 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: sec-check <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- changelog.d/security-370-powered-by-header.md | 2 ++ next.config.ts | 2 ++ src/__tests__/next-config-headers.test.ts | 8 ++++++++ 3 files changed, 12 insertions(+) create mode 100644 changelog.d/security-370-powered-by-header.md 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 00000000..77b35f79 --- /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 516cd852..545c4dc6 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 950eb9c9..860090c9 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]);