diff --git a/desktop-shell/tauri-skeleton/README.md b/desktop-shell/tauri-skeleton/README.md index f0ced63..456d5f0 100644 --- a/desktop-shell/tauri-skeleton/README.md +++ b/desktop-shell/tauri-skeleton/README.md @@ -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` diff --git a/desktop-shell/tauri-skeleton/RELEASE.md b/desktop-shell/tauri-skeleton/RELEASE.md index e953722..03e6f8a 100644 --- a/desktop-shell/tauri-skeleton/RELEASE.md +++ b/desktop-shell/tauri-skeleton/RELEASE.md @@ -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/` diff --git a/desktop-shell/tauri-skeleton/scripts/build-frontend-export.cjs b/desktop-shell/tauri-skeleton/scripts/build-frontend-export.cjs index a67d9f3..fc44fbd 100644 --- a/desktop-shell/tauri-skeleton/scripts/build-frontend-export.cjs +++ b/desktop-shell/tauri-skeleton/scripts/build-frontend-export.cjs @@ -17,7 +17,7 @@ const excludedPaths = [ '.next', 'out', 'src/app/api', - 'src/middleware.ts', + 'src/proxy.ts', ]; function normalizeRelativePath(target) { diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 9d9ee2a..a8818f7 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -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. diff --git a/frontend/src/__tests__/csp/cspNoncePlumbing.test.ts b/frontend/src/__tests__/csp/cspNoncePlumbing.test.ts index b17d7b0..f7a4695 100644 --- a/frontend/src/__tests__/csp/cspNoncePlumbing.test.ts +++ b/frontend/src/__tests__/csp/cspNoncePlumbing.test.ts @@ -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 ^/$ 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); }); diff --git a/frontend/src/__tests__/csp/cspProductionHardening.test.ts b/frontend/src/__tests__/csp/cspProductionHardening.test.ts index fe5d577..dab662e 100644 --- a/frontend/src/__tests__/csp/cspProductionHardening.test.ts +++ b/frontend/src/__tests__/csp/cspProductionHardening.test.ts @@ -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); } diff --git a/frontend/src/middleware.ts b/frontend/src/proxy.ts similarity index 96% rename from frontend/src/middleware.ts rename to frontend/src/proxy.ts index e6cc998..cc6f223 100644 --- a/frontend/src/middleware.ts +++ b/frontend/src/proxy.ts @@ -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