fix(security): check request sources, regions and endpoints

- Bedrock: a request's AWS region must be a region name. It becomes part
  of the endpoint's host name, so a value such as
  "us-east-1.attacker.example/" sent the server's bearer token or signed
  request to another host.
- MCP preview server: only the preview page itself (Origin equal to the
  Host) or a non-browser client may call it; a page on another localhost
  port could replace the diagram with a plain text POST. History builds
  its thumbnails element by element and shows only SVG data images, so a
  stored value can no longer run script in the preview.
- chat, validate-model, validate-diagram, provider-models and parse-url
  take JSON bodies only, so another website cannot make the user's own
  server (the desktop app, a local install) run models with their keys;
  the desktop app also refuses a foreign Host (DNS rebinding).
- The model list reads at most 2 MB, also through the Gateway SDK, and
  answers only with its own error texts: the URL is the caller's and may
  be an internal address.
- An admin panel provider with its own key and no URL no longer inherits
  the global <P>_BASE_URL, which may be a proxy for another key; OpenAI
  then gets the official endpoint, as its Test. Azure keeps the server's
  resource.
This commit is contained in:
dayuan.jiang
2026-10-05 17:02:06 +09:00
parent 50c7ad3ec4
commit 4731394f32
19 changed files with 488 additions and 59 deletions
+3 -1
View File
@@ -1,7 +1,7 @@
import { streamText, tool } from "ai"
import { NextResponse } from "next/server"
import { z } from "zod"
import { checkAccessCode } from "@/lib/access-code"
import { checkAccessCode, rejectCrossSite } from "@/lib/access-code"
import { checkAdminAuth } from "@/lib/admin/auth"
import { getAIModel, usesServerCredentials } from "@/lib/ai-providers"
import { classifyLLMError } from "@/lib/llm-errors"
@@ -35,6 +35,8 @@ const NO_TOOL_CALL_WARNING =
"Connected, but the model answered without calling a tool. It may not support tool calls, which drawing needs."
export async function POST(req: Request) {
const crossSite = rejectCrossSite(req)
if (crossSite) return crossSite
// Lets the server send requests to arbitrary URLs, so require the access
// code, or the admin password (the admin panel's Test button)
const accessError = checkAccessCode(req)