diff --git a/gateway/platforms/api_server.py b/gateway/platforms/api_server.py index e395516100..4300f5da57 100644 --- a/gateway/platforms/api_server.py +++ b/gateway/platforms/api_server.py @@ -24,6 +24,7 @@ import hmac import json import logging import os +import re import sqlite3 import time import uuid @@ -574,6 +575,12 @@ class APIServerAdapter(BasePlatformAdapter): ), status=403, ) + # Sanitize: reject control characters that could enable header injection. + if re.search(r'[\r\n\x00]', provided_session_id): + return web.json_response( + {"error": {"message": "Invalid session ID", "type": "invalid_request_error"}}, + status=400, + ) session_id = provided_session_id try: db = self._ensure_session_db() diff --git a/gateway/platforms/dingtalk.py b/gateway/platforms/dingtalk.py index 8ed3769624..e83b902dfb 100644 --- a/gateway/platforms/dingtalk.py +++ b/gateway/platforms/dingtalk.py @@ -20,6 +20,7 @@ Configuration in config.yaml: import asyncio import logging import os +import re import time import uuid from datetime import datetime, timezone @@ -54,6 +55,8 @@ MAX_MESSAGE_LENGTH = 20000 DEDUP_WINDOW_SECONDS = 300 DEDUP_MAX_SIZE = 1000 RECONNECT_BACKOFF = [2, 5, 10, 30, 60] +_SESSION_WEBHOOKS_MAX = 500 +_DINGTALK_WEBHOOK_RE = re.compile(r'^https://api\.dingtalk\.com/') def check_dingtalk_requirements() -> bool: @@ -195,9 +198,15 @@ class DingTalkAdapter(BasePlatformAdapter): chat_id = conversation_id or sender_id chat_type = "group" if is_group else "dm" - # Store session webhook for reply routing + # Store session webhook for reply routing (validate origin to prevent SSRF) session_webhook = getattr(message, "session_webhook", None) or "" - if session_webhook and chat_id: + if session_webhook and chat_id and _DINGTALK_WEBHOOK_RE.match(session_webhook): + if len(self._session_webhooks) >= _SESSION_WEBHOOKS_MAX: + # Evict oldest entry to cap memory growth + try: + self._session_webhooks.pop(next(iter(self._session_webhooks))) + except StopIteration: + pass self._session_webhooks[chat_id] = session_webhook source = self.build_source(