Spaces:
Runtime error
Runtime error
| /** | |
| * Next.js 16 Proxy File Contract β Lockdown Test | |
| * | |
| * Next.js 16 deprecated `middleware.ts` in favour of `proxy.ts` (commit | |
| * 3fb72b973 renamed our copy to match the new convention). The framework | |
| * only invokes this file when ALL of the following hold: | |
| * | |
| * 1. The file lives at `src/proxy.ts` (since `src/app` is the app dir). | |
| * 2. It exports a function named exactly `proxy` (or default). | |
| * 3. The function delegates to `runAuthzPipeline` with `enforce: true`. | |
| * 4. The `config.matcher` covers every prefix routes are mounted under, | |
| * so unauthenticated requests cannot slip past the centralized | |
| * authorization tiers (PUBLIC / CLIENT_API / MANAGEMENT). | |
| * | |
| * Without ANY of these guarantees the pipeline silently becomes dead code | |
| * and every `/api/*` route falls back to per-route self-enforcement, which | |
| * is the failure mode that led to the v3.8.4 hardening pass. Lock the | |
| * contract down here so a future rename / refactor cannot regress it | |
| * unnoticed. | |
| * | |
| * @see docs/security/ROUTE_GUARD_TIERS.md | |
| * @see https://nextjs.org/docs/app/api-reference/file-conventions/proxy | |
| */ | |
| import test from "node:test"; | |
| import assert from "node:assert/strict"; | |
| import fs from "node:fs"; | |
| test("Next.js 16 proxy file exists at src/proxy.ts (not src/middleware.ts)", () => { | |
| assert.ok(fs.existsSync("src/proxy.ts"), "src/proxy.ts must exist (Next.js 16 file convention)"); | |
| assert.ok( | |
| !fs.existsSync("src/middleware.ts"), | |
| "src/middleware.ts must NOT exist β Next.js 16 deprecated middleware.ts, the active file is src/proxy.ts" | |
| ); | |
| }); | |
| test("proxy.ts exports a function named 'proxy' (Next.js 16 requires this exact name)", () => { | |
| const content = fs.readFileSync("src/proxy.ts", "utf8"); | |
| assert.match( | |
| content, | |
| /export\s+async\s+function\s+proxy\s*\(/, | |
| "must export `async function proxy(...)` β Next.js 16 only invokes this exact name" | |
| ); | |
| }); | |
| test("proxy.ts delegates to runAuthzPipeline with enforce: true", () => { | |
| const content = fs.readFileSync("src/proxy.ts", "utf8"); | |
| assert.match( | |
| content, | |
| /runAuthzPipeline\([^)]*\{\s*enforce:\s*true\s*\}\s*\)/, | |
| "must call runAuthzPipeline with { enforce: true } β otherwise the pipeline runs in observe-only mode and never blocks" | |
| ); | |
| }); | |
| test("proxy.ts config.matcher covers every /api/* route plus dashboard and v1 aliases", () => { | |
| const content = fs.readFileSync("src/proxy.ts", "utf8"); | |
| // Required prefixes β drop one and the corresponding routes go unguarded. | |
| const requiredMatchers = [ | |
| '"/api/:path*"', | |
| '"/dashboard/:path*"', | |
| '"/v1/:path*"', | |
| '"/chat/:path*"', | |
| '"/responses/:path*"', | |
| '"/codex/:path*"', | |
| '"/models"', | |
| ]; | |
| for (const matcher of requiredMatchers) { | |
| assert.ok( | |
| content.includes(matcher), | |
| `proxy.ts config.matcher must include ${matcher} β otherwise routes under that prefix bypass the authz pipeline` | |
| ); | |
| } | |
| }); | |
| test("proxy.ts does not declare runtime: 'edge' (Next.js 16 proxy is Node-only)", () => { | |
| const content = fs.readFileSync("src/proxy.ts", "utf8"); | |
| assert.ok( | |
| !/runtime:\s*['"]edge['"]/.test(content), | |
| "proxy.ts MUST NOT set runtime: 'edge' β Next.js 16 only supports nodejs in proxy.ts. The pipeline depends on Node-only modules (jose, better-sqlite3) and would crash at request time on the edge runtime." | |
| ); | |
| }); | |