mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
fix: deny-by-default API auth + safe SSE controller
This commit is contained in:
+44
-12
@@ -18,29 +18,60 @@ async function hasValidCliToken(request) {
|
||||
return token === await getCliToken();
|
||||
}
|
||||
|
||||
// Public API paths — no auth required (LLM API has its own key auth inside handler).
|
||||
const PUBLIC_API_PATHS = [
|
||||
"/api/health",
|
||||
"/api/init",
|
||||
"/api/locale",
|
||||
"/api/auth/login",
|
||||
"/api/auth/logout",
|
||||
"/api/auth/status",
|
||||
"/api/auth/oidc",
|
||||
"/api/version",
|
||||
"/api/settings/require-login",
|
||||
];
|
||||
|
||||
// Public top-level prefixes (LLM API endpoints with their own API key auth).
|
||||
const PUBLIC_PREFIXES = ["/v1", "/v1beta"];
|
||||
|
||||
// Always require JWT token regardless of requireLogin setting
|
||||
const ALWAYS_PROTECTED = [
|
||||
"/api/shutdown",
|
||||
"/api/settings/database",
|
||||
"/api/version/shutdown",
|
||||
"/api/version/update",
|
||||
"/api/oauth/cursor/auto-import",
|
||||
"/api/oauth/kiro/auto-import",
|
||||
];
|
||||
|
||||
// Require auth, but allow through if requireLogin is disabled
|
||||
const PROTECTED_API_PATHS = [
|
||||
"/api/settings",
|
||||
"/api/keys",
|
||||
"/api/providers/client",
|
||||
"/api/provider-nodes/validate",
|
||||
"/api/providers",
|
||||
"/api/provider-nodes",
|
||||
"/api/proxy-pools",
|
||||
"/api/combos",
|
||||
"/api/models",
|
||||
"/api/usage",
|
||||
"/api/oauth",
|
||||
"/api/cloud",
|
||||
"/api/media-providers",
|
||||
"/api/pricing",
|
||||
"/api/tags",
|
||||
"/api/cli-tools",
|
||||
"/api/mcp",
|
||||
"/api/translator",
|
||||
"/api/tunnel",
|
||||
];
|
||||
|
||||
// Routes that spawn child processes — restrict to localhost regardless of auth.
|
||||
// Routes that spawn child processes or read host secrets — restrict to localhost.
|
||||
const LOCAL_ONLY_PATHS = [
|
||||
"/api/cli-tools/cowork-settings",
|
||||
"/api/mcp/",
|
||||
"/api/tunnel/tailscale-install",
|
||||
"/api/oauth/cursor/auto-import",
|
||||
"/api/oauth/kiro/auto-import",
|
||||
];
|
||||
|
||||
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
|
||||
@@ -85,13 +116,18 @@ async function isAuthenticated(request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isPublicApi(pathname) {
|
||||
if (PUBLIC_PREFIXES.some((p) => pathname === p || pathname.startsWith(`${p}/`))) return true;
|
||||
return PUBLIC_API_PATHS.some((p) => pathname === p || pathname.startsWith(`${p}/`));
|
||||
}
|
||||
|
||||
export async function proxy(request) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
// Local-only gate for spawn-capable routes (CVE GHSA-fhh6-4qxv-rpqj).
|
||||
// Local-only gate for spawn-capable / host-secret routes.
|
||||
if (LOCAL_ONLY_PATHS.some((p) => pathname.startsWith(p))) {
|
||||
if (!isLocalRequest(request)) {
|
||||
return NextResponse.json({ error: "Local only: MCP requires localhost access" }, { status: 403 });
|
||||
return NextResponse.json({ error: "Local only: loopback access required" }, { status: 403 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,9 +138,9 @@ export async function proxy(request) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
// Protect sensitive API endpoints (allow CLI token, JWT, or requireLogin=false)
|
||||
if (PROTECTED_API_PATHS.some((p) => pathname.startsWith(p))) {
|
||||
if (pathname === "/api/settings/require-login") return NextResponse.next();
|
||||
// Deny-by-default for /api/* — public allow-list bypasses, everything else requires auth.
|
||||
if (pathname.startsWith("/api/")) {
|
||||
if (isPublicApi(pathname)) return NextResponse.next();
|
||||
if (await hasValidCliToken(request) || await isAuthenticated(request))
|
||||
return NextResponse.next();
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
@@ -158,7 +194,3 @@ export async function proxy(request) {
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/", "/dashboard/:path*"],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user