Files
OmniRoute/open-sse/config/datarobot.ts
diegosouzapw 9e198184a7 fix(security): resolve 14 CodeQL code scanning alerts
- Replace polynomial regex /\/+$/ with loop-based stripTrailingSlashes()
  across 8 enterprise provider configs (azure-openai, azureAi, bedrock,
  datarobot, oci, sap, watsonx, audioSpeech) — fixes js/polynomial-redos

- Add prototype-pollution denylist guard in usageHistory.ts to reject
  __proto__/constructor/prototype as model keys — fixes
  js/prototype-polluting-assignment (#167, #168)

- Suppress 3 false-positive js/insufficient-password-hash alerts in
  chatgpt-web.ts and builtins.ts where SHA-256 is used for cache-key
  derivation, not password storage (#176, #177, #178)

- Add stripTrailingSlashes unit tests with ReDoS regression check
2026-04-27 20:00:10 -03:00

74 lines
2.4 KiB
TypeScript

import { stripTrailingSlashes } from "../utils/urlSanitize.ts";
const DATAROBOT_API_V2_SEGMENT = "/api/v2";
const DATAROBOT_LLMGW_CHAT_PATH = "/genai/llmgw/chat/completions/";
const DATAROBOT_LLMGW_CATALOG_PATH = "/genai/llmgw/catalog/";
export const DATAROBOT_DEFAULT_BASE_URL = "https://app.datarobot.com";
function normalizeBaseUrl(value: string | null | undefined): string {
return stripTrailingSlashes((value || "").trim());
}
export function normalizeDataRobotBaseUrl(value: string | null | undefined): string {
const normalized = normalizeBaseUrl(value || DATAROBOT_DEFAULT_BASE_URL);
return normalized || DATAROBOT_DEFAULT_BASE_URL;
}
export function isDataRobotDeploymentUrl(value: string | null | undefined): boolean {
const normalized = normalizeDataRobotBaseUrl(value);
return /\/api\/v2\/deployments\/[^/]+(?:\/chat\/completions)?$/i.test(normalized);
}
export function buildDataRobotChatUrl(value: string | null | undefined): string {
const normalized = normalizeDataRobotBaseUrl(value);
if (normalized.endsWith("/chat/completions")) {
return normalized;
}
if (/\/api\/v2\/deployments\/[^/]+$/i.test(normalized)) {
return `${normalized}/chat/completions`;
}
if (/\/api\/v2\/genai\/llmgw$/i.test(normalized)) {
return `${normalized}/chat/completions/`;
}
if (/\/api\/v2\/genai\/llmgw\/chat$/i.test(normalized)) {
return `${normalized}/completions/`;
}
if (normalized.includes(DATAROBOT_API_V2_SEGMENT)) {
return `${normalized}${DATAROBOT_LLMGW_CHAT_PATH}`;
}
return `${normalized}${DATAROBOT_API_V2_SEGMENT}${DATAROBOT_LLMGW_CHAT_PATH}`;
}
export function buildDataRobotCatalogUrl(value: string | null | undefined): string | null {
const normalized = normalizeDataRobotBaseUrl(value);
if (isDataRobotDeploymentUrl(normalized)) {
return null;
}
const parsed = new URL(normalized);
let basePath = stripTrailingSlashes(parsed.pathname);
if (/\/api\/v2\/genai\/llmgw\/chat\/completions$/i.test(basePath)) {
basePath = basePath.replace(/\/chat\/completions$/i, "");
} else if (/\/api\/v2\/genai\/llmgw$/i.test(basePath)) {
// Keep path as-is.
} else if (basePath.includes(DATAROBOT_API_V2_SEGMENT)) {
basePath = basePath.replace(/\/api\/v2.*$/i, "");
}
const catalogPath = `${basePath}${DATAROBOT_LLMGW_CATALOG_PATH}`.replace(/\/{2,}/g, "/");
parsed.pathname = catalogPath;
parsed.search = "";
parsed.hash = "";
return parsed.toString();
}