Repository Public MIT
1import { NextResponse, type NextRequest } from "next/server";23/**4 * Per-request concerns that must run before rendering:5 * - a nonce-based Content-Security-Policy (no 'unsafe-inline' scripts: package READMEs are untrusted);6 * - pretty artifact URLs: /@namespace/slug → /a/namespace/slug;7 * - /mcp is the MCP protocol endpoint (POST); a browser visiting it gets the MCP server listing.8 */9export function proxy(req: NextRequest) {10 const { pathname } = req.nextUrl;11 const nonce = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))));12 const dev = process.env.NODE_ENV !== "production";13 const csp = [14 "default-src 'self'",15 `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'${dev ? " 'unsafe-eval'" : ""}`,16 "style-src 'self' 'unsafe-inline'",17 "img-src 'self' data:",18 "font-src 'self' data:",19 `connect-src 'self'${dev ? " ws:" : ""}`,20 "object-src 'none'",21 "base-uri 'self'",22 "form-action 'self'",23 "frame-ancestors 'none'",24 ].join("; ");2526 const headers = new Headers(req.headers);27 headers.set("x-nonce", nonce);28 headers.set("content-security-policy", csp);2930 let res: NextResponse;31 if (pathname.startsWith("/dashboard") && !req.cookies.has("skills_session")) {32 // Cheap early exit; the real check (session lookup) happens in every dashboard page and action.33 return NextResponse.redirect(new URL("/login", req.url));34 }35 if (pathname.startsWith("/@")) {36 const url = req.nextUrl.clone();37 url.pathname = "/a/" + pathname.slice(2);38 res = NextResponse.rewrite(url, { request: { headers } });39 } else if (pathname === "/mcp" && req.method === "GET" && (req.headers.get("accept") ?? "").includes("text/html")) {40 const url = req.nextUrl.clone();41 url.pathname = "/mcp-servers";42 res = NextResponse.rewrite(url, { request: { headers } });43 } else {44 res = NextResponse.next({ request: { headers } });45 }46 res.headers.set("Content-Security-Policy", csp);47 return res;48}4950export const config = {51 matcher: [{ source: "/((?!api/|_next/static|_next/image|cli/|favicon.ico|icon.svg|robots.txt|sitemap.xml).*)" }],52};53