mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 07:12:12 +03:00
fix(security): remove quadratic trailing-slash trim in Alibaba endpoint normalization
CodeQL js/polynomial-redos (alerts #765/#766). `/\/+$/` has no left anchor, so the engine retries the match at every start offset and each attempt re-walks the whole slash run before failing `$` — O(n^2) on a connection baseUrl made of many slashes. Measured on the vulnerable code: 10k slashes = 102ms, 30k = 968ms, 60k = 4028ms (clean quadratic); through the public resolvers the same input took 22s. providerSpecificData.baseUrl is operator-supplied config and reaches the trim via isFamilyPresetUrl() -> normalizeEndpoint() and both resolve*Url() helpers, so the input is reachable. Replaces all three identical occurrences with a linear charCodeAt scan. CodeQL only flagged two of them; the third (resolveAlibabaProviderModelsUrl) carries the same defect and is fixed here rather than left behind. Behavior is unchanged: every trailing slash is still removed (not a bounded subset), interior slashes are preserved, and an all-slash string still collapses to empty — asserted by the new behavior test.
This commit is contained in:
@@ -52,10 +52,24 @@ function canonicalProviderFamily(providerId: string): AlibabaProviderFamily | nu
|
||||
return null;
|
||||
}
|
||||
|
||||
const SLASH_CHAR_CODE = 47;
|
||||
|
||||
/**
|
||||
* Strip every trailing "/" in linear time.
|
||||
*
|
||||
* Deliberately not a regex: `/\/+$/` has no left anchor, so the engine retries at
|
||||
* every start offset and each attempt re-walks the slash run before failing `$` —
|
||||
* quadratic on an endpoint made of many slashes (CodeQL js/polynomial-redos).
|
||||
* Connection base URLs are operator-supplied, so they reach this trim directly.
|
||||
*/
|
||||
function stripTrailingSlashes(value: string): string {
|
||||
let end = value.length;
|
||||
while (end > 0 && value.charCodeAt(end - 1) === SLASH_CHAR_CODE) end--;
|
||||
return end === value.length ? value : value.slice(0, end);
|
||||
}
|
||||
|
||||
function normalizeEndpoint(value: string): string {
|
||||
return value
|
||||
.trim()
|
||||
.replace(/\/+$/, "")
|
||||
return stripTrailingSlashes(value.trim())
|
||||
.replace(/\/(?:chat\/completions|messages)$/i, "")
|
||||
.toLowerCase();
|
||||
}
|
||||
@@ -138,10 +152,9 @@ export function resolveAlibabaProviderModelsUrl(
|
||||
providerSpecificData?: unknown,
|
||||
fallback = ""
|
||||
): string {
|
||||
const baseUrl = resolveAlibabaProviderBaseUrl(providerId, providerSpecificData, fallback)
|
||||
.trim()
|
||||
.replace(/\/+$/, "")
|
||||
.replace(/\/(?:chat\/completions|messages|models)$/i, "");
|
||||
const baseUrl = stripTrailingSlashes(
|
||||
resolveAlibabaProviderBaseUrl(providerId, providerSpecificData, fallback).trim()
|
||||
).replace(/\/(?:chat\/completions|messages|models)$/i, "");
|
||||
return baseUrl ? `${baseUrl}/models` : "";
|
||||
}
|
||||
|
||||
@@ -154,9 +167,9 @@ export function resolveAlibabaProviderMediaBaseUrl(
|
||||
providerSpecificData?: unknown,
|
||||
fallback = ""
|
||||
): string {
|
||||
return resolveAlibabaProviderBaseUrl(providerId, providerSpecificData, fallback)
|
||||
.trim()
|
||||
.replace(/\/+$/, "")
|
||||
return stripTrailingSlashes(
|
||||
resolveAlibabaProviderBaseUrl(providerId, providerSpecificData, fallback).trim()
|
||||
)
|
||||
.replace(/\/compatible-mode\/v1(?:\/(?:chat\/completions|models))?$/i, "/api/v1")
|
||||
.replace(/\/apps\/anthropic(?:\/v1)?(?:\/messages)?$/i, "/api/v1");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user