diff --git a/open-sse/config/providerRegistry.ts b/open-sse/config/providerRegistry.ts index cd0f2b7959..83871152c2 100644 --- a/open-sse/config/providerRegistry.ts +++ b/open-sse/config/providerRegistry.ts @@ -946,6 +946,35 @@ export const REGISTRY: Record = { { id: "claude-sonnet-4-5@20251101", name: "Claude Sonnet 4.5 (Vertex)" }, ], }, + + alibaba: { + id: "alibaba", + alias: "ali", + format: "openai", + executor: "default", + // DashScope international OpenAI-compatible endpoint. + // China users should set providerSpecificData.baseUrl to: + // https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions + baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions", + modelsUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models", + authType: "apikey", + authHeader: "bearer", + models: [ + { id: "qwen-max", name: "Qwen Max" }, + { id: "qwen-max-2025-01-25", name: "Qwen Max (2025-01-25)" }, + { id: "qwen-plus", name: "Qwen Plus" }, + { id: "qwen-plus-2025-07-14", name: "Qwen Plus (2025-07-14)" }, + { id: "qwen-turbo", name: "Qwen Turbo" }, + { id: "qwen-turbo-2025-11-01", name: "Qwen Turbo (2025-11-01)" }, + { id: "qwen3-coder-plus", name: "Qwen3 Coder Plus" }, + { id: "qwen3-coder-flash", name: "Qwen3 Coder Flash" }, + { id: "qwq-plus", name: "QwQ Plus (Reasoning)" }, + { id: "qwq-32b", name: "QwQ 32B" }, + { id: "qwen3-32b", name: "Qwen3 32B" }, + { id: "qwen3-235b-a22b", name: "Qwen3 235B A22B" }, + ], + passthroughModels: true, + }, }; // ── Generator Functions ─────────────────────────────────────────────────── diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 12489d67e7..6407080f17 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -2,6 +2,20 @@ import { HTTP_STATUS, FETCH_TIMEOUT_MS } from "../config/constants.ts"; import { applyFingerprint, isCliCompatEnabled } from "../config/cliFingerprints.ts"; import { getRotatingApiKey } from "../services/apiKeyRotator.ts"; +/** + * Sanitizes a custom API path to prevent path traversal attacks. + * Valid paths must start with '/', contain no '..' segments, + * no null bytes, and be reasonable in length. + */ +function sanitizePath(path: string): boolean { + if (typeof path !== "string") return false; + if (!path.startsWith("/")) return false; + if (path.includes("\0")) return false; // null byte + if (path.includes("..")) return false; // path traversal + if (path.length > 512) return false; // sanity limit + return true; +} + type JsonRecord = Record; export type ProviderConfig = { @@ -102,7 +116,9 @@ export class BaseExecutor { const psd = credentials?.providerSpecificData; const baseUrl = typeof psd?.baseUrl === "string" ? psd.baseUrl : "https://api.openai.com/v1"; const normalized = baseUrl.replace(/\/$/, ""); - const customPath = typeof psd?.chatPath === "string" && psd.chatPath ? psd.chatPath : null; + // Sanitize custom path: must start with '/', no path traversal, no null bytes + const rawPath = typeof psd?.chatPath === "string" && psd.chatPath ? psd.chatPath : null; + const customPath = rawPath && sanitizePath(rawPath) ? rawPath : null; if (customPath) return `${normalized}${customPath}`; const path = this.provider.includes("responses") ? "/responses" : "/chat/completions"; return `${normalized}${path}`; diff --git a/src/shared/constants/providers.ts b/src/shared/constants/providers.ts index dd6ca4dcb8..84cf5dfcd7 100644 --- a/src/shared/constants/providers.ts +++ b/src/shared/constants/providers.ts @@ -370,6 +370,16 @@ export const APIKEY_PROVIDERS = { website: "https://cloud.google.com/vertex-ai", authHint: "Provide Service Account JSON or OAuth access_token", }, + alibaba: { + id: "alibaba", + alias: "ali", + name: "Alibaba Cloud (DashScope)", + icon: "cloud_queue", + color: "#FF6600", + textIcon: "AL", + website: "https://dashscope-intl.aliyuncs.com", + hasFree: false, + }, }; export const OPENAI_COMPATIBLE_PREFIX = "openai-compatible-";