mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-06-30 17:55:35 +02:00
Migrate CSP middleware to Next.js proxy convention.
Renames src/middleware.ts to src/proxy.ts per Next.js 16 deprecation guidance. CSP nonce behavior is unchanged; build no longer emits the middleware warning. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -75,7 +75,7 @@ The release build now does the full packaging pipeline:
|
||||
|
||||
1. Generates branded icons in `src-tauri/icons/`
|
||||
2. Stages a desktop-only frontend export tree that omits Next server-only
|
||||
routes/middleware (`src/app/api`, `src/middleware.ts`)
|
||||
routes/proxy (`src/app/api`, `src/proxy.ts`)
|
||||
3. Stages a managed backend runtime bundle from `backend/` into
|
||||
`src-tauri/backend-runtime/`
|
||||
4. Builds the frontend export with `NEXT_OUTPUT=export`
|
||||
|
||||
@@ -52,7 +52,7 @@ See [RELEASE_INPUTS.md](./RELEASE_INPUTS.md) for the plain-language answer to
|
||||
|
||||
1. Generates the desktop icon set in `src-tauri/icons/`
|
||||
2. Stages a desktop-only frontend export tree that omits Next server-only
|
||||
routes/middleware (`src/app/api`, `src/middleware.ts`)
|
||||
routes/proxy (`src/app/api`, `src/proxy.ts`)
|
||||
3. Stages a managed backend runtime bundle into `src-tauri/backend-runtime/`
|
||||
4. Builds the frontend export with `NEXT_OUTPUT=export`
|
||||
5. Copies `frontend/out` into `src-tauri/companion-www/`
|
||||
|
||||
@@ -17,7 +17,7 @@ const excludedPaths = [
|
||||
'.next',
|
||||
'out',
|
||||
'src/app/api',
|
||||
'src/middleware.ts',
|
||||
'src/proxy.ts',
|
||||
];
|
||||
|
||||
function normalizeRelativePath(target) {
|
||||
|
||||
@@ -9,12 +9,12 @@ const skipTypecheck = process.env.NEXT_SKIP_TYPECHECK === '1';
|
||||
|
||||
// Desktop packaging: set NEXT_OUTPUT=export to produce a static export
|
||||
// (frontend/out/) suitable for Tauri bundling and companion server hosting.
|
||||
// This disables API routes, middleware, and server-side image optimization —
|
||||
// This disables API routes, proxy, and server-side image optimization —
|
||||
// all handled by the Tauri shell and companion server in packaged mode.
|
||||
// Default remains 'standalone' for the web deployment (Docker/Vercel).
|
||||
const isDesktopExport = process.env.NEXT_OUTPUT === 'export';
|
||||
|
||||
// CSP is now emitted dynamically by src/middleware.ts (Phase 5F-A) so that
|
||||
// CSP is now emitted dynamically by src/proxy.ts (Phase 5F-A) so that
|
||||
// each document response carries a unique per-request nonce. Non-CSP
|
||||
// security headers remain here because they are static and benefit from
|
||||
// next.config's catch-all source matcher.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 1. Document CSP remains hydration-safe for the Next.js runtime
|
||||
* 2. CSP is deterministic across repeated requests
|
||||
* 3. next.config.ts no longer owns a static CSP header
|
||||
* 4. Middleware does not break API/static routes (matcher exclusion)
|
||||
* 4. Proxy does not break API/static routes (matcher exclusion)
|
||||
* 5. Google Fonts domains are preserved in CSP
|
||||
* 6. Production CSP preserves required directives
|
||||
*/
|
||||
@@ -13,26 +13,26 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { NextRequest } from 'next/server';
|
||||
|
||||
import { middleware, config as middlewareConfig } from '@/middleware';
|
||||
import { proxy, config as proxyConfig } from '@/proxy';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Call middleware with a fake document request and return the response. */
|
||||
function callMiddleware(path = '/') {
|
||||
/** Call proxy with a fake document request and return the response. */
|
||||
function callProxy(path = '/') {
|
||||
const req = new NextRequest(`http://localhost${path}`, { method: 'GET' });
|
||||
return middleware(req);
|
||||
return proxy(req);
|
||||
}
|
||||
|
||||
/** Extract the CSP header string from a middleware response. */
|
||||
/** Extract the CSP header string from a proxy response. */
|
||||
function getCsp(path = '/'): string {
|
||||
return callMiddleware(path).headers.get('Content-Security-Policy') ?? '';
|
||||
return callProxy(path).headers.get('Content-Security-Policy') ?? '';
|
||||
}
|
||||
|
||||
/** Check whether the middleware matcher regex excludes a given path. */
|
||||
/** Check whether the proxy matcher regex excludes a given path. */
|
||||
function matcherExcludes(path: string): boolean {
|
||||
const pattern = middlewareConfig.matcher[0];
|
||||
const pattern = proxyConfig.matcher[0];
|
||||
// Next.js wraps the matcher in ^/<pattern>$ for path matching.
|
||||
// We replicate the essential check: the negative-lookahead prefix groups.
|
||||
const re = new RegExp(`^${pattern}$`);
|
||||
@@ -55,7 +55,7 @@ describe('hydration-safe CSP header', () => {
|
||||
expect(csp).toMatch(/script-src [^;]*'unsafe-inline'/);
|
||||
});
|
||||
|
||||
it('middleware still returns a CSP header for document requests', () => {
|
||||
it('proxy still returns a CSP header for document requests', () => {
|
||||
const csp = getCsp();
|
||||
expect(csp).toContain("default-src 'self'");
|
||||
expect(csp).toContain("script-src 'self'");
|
||||
@@ -117,10 +117,10 @@ describe('next.config.ts CSP removal', () => {
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 4. Middleware does not break API/static routes
|
||||
// 4. Proxy does not break API/static routes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('middleware matcher exclusions', () => {
|
||||
describe('proxy matcher exclusions', () => {
|
||||
it('excludes /api paths', () => {
|
||||
expect(matcherExcludes('/api/mesh/events')).toBe(true);
|
||||
});
|
||||
|
||||
@@ -13,19 +13,19 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { NextRequest } from 'next/server';
|
||||
|
||||
import { middleware, config as middlewareConfig } from '@/middleware';
|
||||
import { proxy, config as proxyConfig } from '@/proxy';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function callMiddleware(path = '/') {
|
||||
function callProxy(path = '/') {
|
||||
const req = new NextRequest(`http://localhost${path}`, { method: 'GET' });
|
||||
return middleware(req);
|
||||
return proxy(req);
|
||||
}
|
||||
|
||||
function getCsp(path = '/'): string {
|
||||
return callMiddleware(path).headers.get('Content-Security-Policy') ?? '';
|
||||
return callProxy(path).headers.get('Content-Security-Policy') ?? '';
|
||||
}
|
||||
|
||||
/** Extract a single CSP directive by name. */
|
||||
@@ -36,7 +36,7 @@ function getDirective(name: string, csp?: string): string {
|
||||
}
|
||||
|
||||
function matcherExcludes(path: string): boolean {
|
||||
const pattern = middlewareConfig.matcher[0];
|
||||
const pattern = proxyConfig.matcher[0];
|
||||
const re = new RegExp(`^${pattern}$`);
|
||||
return !re.test(path);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Phase 5F-A: CSP nonce plumbing middleware.
|
||||
* Phase 5F-A: CSP nonce plumbing proxy.
|
||||
*
|
||||
* Generates a per-request cryptographic nonce and emits a dynamic
|
||||
* Content-Security-Policy header for document (page) responses.
|
||||
@@ -36,7 +36,7 @@ function buildCsp(nonce: string, strictScripts = false): string {
|
||||
return directives.join('; ');
|
||||
}
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
export function proxy(request: NextRequest) {
|
||||
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
|
||||
|
||||
// Forward a nonce for staged CSP support. Strict script-src is opt-in until
|
||||
Reference in New Issue
Block a user