feat(provider): add Alibaba Cloud DashScope + path validation for custom endpoint paths

- Add Alibaba Cloud (DashScope) as OpenAI-compatible provider with 12 Qwen models:
  qwen-max, qwen-plus, qwen-turbo, qwen3-coder-plus/flash, qwq-plus,
  qwq-32b, qwen3-32b, qwen3-235b-a22b
  International endpoint: dashscope-intl.aliyuncs.com/compatible-mode/v1
  Auth: Bearer API key (same as groq/xai/mistral)

- Add path traversal protection to custom endpoint paths (PR #400):
  sanitizePath() validates chatPath/modelsPath values:
  must start with '/', no '..' segments, no null bytes, max 512 chars

Closes #400 (custom endpoint paths), part of Alibaba provider integration
This commit is contained in:
diegosouzapw
2026-03-16 09:44:17 -03:00
parent cd05e03d63
commit 605c3f9be1
3 changed files with 56 additions and 1 deletions

View File

@@ -946,6 +946,35 @@ export const REGISTRY: Record<string, RegistryEntry> = {
{ 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 ───────────────────────────────────────────────────

View File

@@ -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<string, unknown>;
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}`;

View File

@@ -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-";