From aa0d0059251882c781ea0c676629d550c93b6b29 Mon Sep 17 00:00:00 2001 From: hmo Date: Sat, 15 Aug 2026 17:37:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20router=20firewall=20=E2=80=94=20validat?= =?UTF-8?q?e=20model=20against=20OCG=20whitelist=20+=20require=20messages;?= =?UTF-8?q?=20block=20client=20errors=20before=20upstream=20call=20(preven?= =?UTF-8?q?ts=20rate-limit=20pollution)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gateway/scripts/ocg_router.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/gateway/scripts/ocg_router.py b/gateway/scripts/ocg_router.py index 7416a2e..f82afb2 100644 --- a/gateway/scripts/ocg_router.py +++ b/gateway/scripts/ocg_router.py @@ -459,6 +459,17 @@ class RouterHandler(BaseHTTPRequestHandler): self._json_response({"object": "list", "data": models}) # ── Chat Completions Proxy ── + # 防火墙:OCG 已知模型白名单(2026-08-15 来自官方文档 go.mdx) + # 请求 model 不在白名单 → 直接 400,不调用上游,防止错误模型名打到上游触发限流 + KNOWN_OCG_MODELS = { + "deepseek-v4-flash", "deepseek-v4-pro", "kimi-k2.7-code", "kimi-k2.6", + "kimi-k2.5", "kimi-k3", "glm-5", "glm-5.1", "glm-5.2", "glm-5.3", + "gpt-5.6-luna", "grok-4.5", "hy3", "hy3-preview", "mimo-v2-omni", + "mimo-v2-pro", "mimo-v2.5", "mimo-v2.5-pro", "minimax-m2.5", + "minimax-m2.7", "minimax-m3", "qwen3.5-plus", "qwen3.6-plus", + "qwen3.7-max", "qwen3.7-plus", "qwen3.8-max", + } + def _proxy_chat(self): start = time.time() content_length = int(self.headers.get("Content-Length", 0)) @@ -466,6 +477,7 @@ class RouterHandler(BaseHTTPRequestHandler): is_stream = False model_name = "unknown" + req_data = None if body: try: req_data = json.loads(body) @@ -475,6 +487,21 @@ class RouterHandler(BaseHTTPRequestHandler): except (json.JSONDecodeError, UnicodeDecodeError) as e: log.warning("failed to parse request body (len=%d): %s", len(body), e) + # ── 防火墙:前置校验,阻止客户端错误传导到上游 ── + if not body or not req_data: + self._error_response(400, "Empty or invalid request body") + return + if not model_name or model_name == "unknown": + self._error_response(400, "Missing required field: model") + return + if "messages" not in req_data or not isinstance(req_data.get("messages"), list) or not req_data["messages"]: + self._error_response(400, "Missing required field: messages (non-empty list)") + return + if model_name not in self.KNOWN_OCG_MODELS: + log.warning("firewall: blocked unknown model '%s' (client error, no upstream call)", model_name) + self._error_response(400, f"Model '{model_name}' is not supported by this router") + return + # 2026-08-13 并发模式:header X-OCG-Mode: concurrent → round-robin 锁 key 轮询 concurrent_mode = self.headers.get("X-OCG-Mode", "").lower() == "concurrent"