From ec65b767cb89af9895df25fcb073d4c507ae1229 Mon Sep 17 00:00:00 2001 From: Keiji Isogimi Date: Tue, 3 Mar 2026 01:39:46 +0000 Subject: [PATCH] docs: add security-hardened proxy.ts example and README section Add a proxy.ts to the app-router-cloudflare example with: - OWASP-recommended security response headers - Double-encoded path traversal detection - Explicit route matcher covering /api/* routes Add a 'Security hardening' section to the README pointing users to the example. This helps vinext users adopt production security defaults without having to discover best practices independently. --- README.md | 9 +++++ examples/app-router-cloudflare/proxy.ts | 47 +++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 examples/app-router-cloudflare/proxy.ts diff --git a/README.md b/README.md index 4a423cd7..c79d14e7 100644 --- a/README.md +++ b/README.md @@ -386,6 +386,15 @@ These are intentional exclusions: - **Node.js production server (`vinext start`)** works for testing but is less complete than Workers deployment. Cloudflare Workers is the primary target. - **Native Node modules (sharp, resvg, satori, lightningcss, @napi-rs/canvas)** crash Vite's RSC dev environment. Dynamic OG image/icon routes using these work in production builds but not in dev mode. These are auto-stubbed during `vinext deploy`. +## Security hardening + +vinext handles URL normalization, path traversal prevention, and internal header stripping automatically. For production deployments, we recommend adding a `proxy.ts` (Next.js 16) or `middleware.ts` (Next.js 15) with: + +1. **Security response headers** — `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy` +2. **Explicit route matcher** — ensures the proxy runs on all paths including `/api/*` + +See [`examples/app-router-cloudflare/proxy.ts`](examples/app-router-cloudflare/proxy.ts) for a ready-to-use template. + ## Benchmarks > **Caveat:** Benchmarks are hard to get right and these are early results. Take them as directional, not definitive. diff --git a/examples/app-router-cloudflare/proxy.ts b/examples/app-router-cloudflare/proxy.ts new file mode 100644 index 00000000..6186480e --- /dev/null +++ b/examples/app-router-cloudflare/proxy.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from 'next/server'; + +/** + * Security-hardened proxy example for vinext on Cloudflare Workers. + * + * This proxy adds: + * 1. Security response headers (OWASP recommended) + * 2. Double-encoded path traversal protection + * + * See: https://owasp.org/www-project-secure-headers/ + */ +export default function proxy(request: NextRequest) { + // Block double-encoded path traversal attempts. + // %252f = double-encoded '/', %2e%2e = encoded '..', %5c = encoded '\' + // These can bypass route matching when the server decodes at different stages. + const rawUrl = request.url; + if (/%25[0-9a-fA-F]{2}/.test(rawUrl) || /%5[cC]/.test(rawUrl)) { + return new NextResponse('Bad Request', { status: 400 }); + } + + const response = NextResponse.next(); + + // Prevent MIME-type sniffing + response.headers.set('X-Content-Type-Options', 'nosniff'); + + // Prevent clickjacking — use 'SAMEORIGIN' if you embed your own pages in iframes + response.headers.set('X-Frame-Options', 'DENY'); + + // Control referrer information sent to other origins + response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin'); + + // Restrict browser features — customize based on your app's needs + response.headers.set( + 'Permissions-Policy', + 'camera=(), microphone=(), geolocation=()', + ); + + return response; +} + +export const config = { + matcher: [ + // Match all paths except static assets and vinext internals. + // This explicit matcher ensures /api/* routes are also covered. + '/((?!_vinext|_next/static|favicon\\.ico).*)', + ], +};