fix(providers): validate bailian-coding-plan against the Token Plan host (#10634)

* fix(providers): validate bailian-coding-plan against the Token Plan host

The catalog entry is the personal Alibaba Token Plan, but the region map still
resolved the retired Coding Plan hosts. #10290 moved only the open-sse registry
(inference) to token-plan.ap-southeast-1.maas.aliyuncs.com, leaving the dashboard's
key validation pointed at coding-intl.dashscope.aliyuncs.com.

That host rejects Token Plan keys with 401, and validateBailianCodingPlanProvider
maps 401/403 to "Invalid API key" — so adding a working key failed at the modal
while the same key served inference fine. Verified live 2026-08-18 with a valid
key: legacy host 401 invalid_api_key, Token Plan host 429 quota (auth OK).

- point both regions of ALIBABA_PROVIDER_ENDPOINTS at the Token Plan hosts,
  matching what docs/providers/ALIBABA-QWEN-PROVIDER-FAMILIES.md already stated
- keep the retired hosts recognized as presets, so connections saved with the old
  URL still follow the region selector instead of being pinned to a dead host
- keep image/video generation on the DashScope AIGC hosts, which the Token Plan
  host does not serve
- probe with a model this plan actually serves (qwen3-coder-plus was Coding Plan)

* test(providers): compare parsed hostnames in the legacy-host guard

CodeQL flags URL .includes() checks as js/incomplete-url-substring-sanitization.
The guard is an assertion, not a sanitizer, but comparing new URL().hostname is
strictly more precise anyway — same coverage, no substring pattern.

---------

Co-authored-by: Xiangzhe <bakryun0718@proton.me>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-18 05:51:34 -03:00
committed by GitHub
parent cd091ab878
commit c164ed962b
11 changed files with 200 additions and 28 deletions

View File

@@ -32,7 +32,7 @@ innych rodzin endpointów, więc wszystkie cztery produkty pozostają osobnymi I
| Rodzina providera | `global-sg` | `china-beijing` | Format wire |
| ----------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------- | ----------- |
| `alibaba` | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` | `https://dashscope.aliyuncs.com/compatible-mode/v1` | OpenAI |
| `bailian-coding-plan` | `https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1` | `https://coding.dashscope.aliyuncs.com/apps/anthropic/v1` | Anthropic |
| `bailian-coding-plan` | `https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1` | `https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic/v1` | Anthropic |
| `qwen-cloud` | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` | `https://dashscope.aliyuncs.com/compatible-mode/v1` | OpenAI |
| `qwen-cloud-token-plan` | `https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1` | `https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1` | OpenAI |

View File

@@ -248,7 +248,7 @@ export const CONFIGURABLE_BASE_URL_PROVIDERS = new Set([
export const DEFAULT_PROVIDER_BASE_URLS: Record<string, string> = {
"azure-openai": "https://example-resource.openai.azure.com",
"azure-ai": "https://example-resource.services.ai.azure.com/openai/v1",
"bailian-coding-plan": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
"bailian-coding-plan": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1",
"xiaomi-mimo": "https://token-plan-sgp.xiaomimimo.com/v1",
siliconflow: "https://api.siliconflow.com/v1",
"searxng-search": "http://localhost:8888/search",

View File

@@ -294,7 +294,9 @@ export async function validateBailianCodingPlanProvider({
providerSpecificData
),
body: JSON.stringify({
model: "qwen3-coder-plus",
// qwen3-coder-plus belonged to the retired Coding Plan host and is absent from
// BAILIAN_CODING_PLAN_MODELS; probe with a model this plan actually serves.
model: providerSpecificData.validationModelId || "qwen3.7-max",
max_tokens: 1,
messages: [{ role: "user", content: "test" }],
}),

View File

@@ -11,9 +11,14 @@ export const ALIBABA_PROVIDER_ENDPOINTS: Readonly<
"global-sg": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
"china-beijing": "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
// The catalog entry is the personal TOKEN Plan (see providers/apikey/regional.ts:
// name "Alibaba Token Plan"). The legacy coding-intl/coding hosts serve the separate
// Coding Plan product and reject Token Plan keys with 401 invalid_api_key — verified
// live 2026-08-18 against the same key that returns 429 (quota) on the host below.
// Keeps /apps/anthropic/v1 because the registry entry is format "claude".
"bailian-coding-plan": {
"global-sg": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
"china-beijing": "https://coding.dashscope.aliyuncs.com/apps/anthropic/v1",
"global-sg": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1",
"china-beijing": "https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic/v1",
},
"qwen-cloud": {
"global-sg": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
@@ -74,11 +79,48 @@ function normalizeEndpoint(value: string): string {
.toLowerCase();
}
/**
* Preset hosts this family used to ship. They must keep counting as presets: a connection
* saved while a preset was current carries that URL in providerSpecificData.baseUrl, and if
* a retired preset were mistaken for a deliberate custom URL the connection would stay
* pinned to a host that no longer accepts its key, deaf to the region selector.
*/
const LEGACY_FAMILY_PRESETS: Readonly<Record<AlibabaProviderFamily, readonly string[]>> = {
alibaba: [],
// Retired 2026-08-18 — Coding Plan hosts, wrong product for this Token Plan entry.
"bailian-coding-plan": [
"https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
"https://coding.dashscope.aliyuncs.com/apps/anthropic/v1",
],
"qwen-cloud": [],
"qwen-cloud-token-plan": [],
};
/**
* Media (AIGC) roots, when they differ from the chat root.
*
* Only bailian-coding-plan diverges: its CHAT traffic moved to the Token Plan host
* (2026-08), but image/video generation keeps running on the DashScope AIGC service
* (`/api/v1/services/aigc/…`) — see imageRegistry.ts / videoRegistry.ts, which pin those
* hosts literally. Deriving media from the chat root would have silently repointed every
* Bailian image/video call at a host that does not serve AIGC.
*/
const ALIBABA_PROVIDER_MEDIA_OVERRIDES: Partial<
Record<AlibabaProviderFamily, Readonly<Record<AlibabaProviderRegion, string>>>
> = {
"bailian-coding-plan": {
"global-sg": "https://coding-intl.dashscope.aliyuncs.com/api/v1",
"china-beijing": "https://coding.dashscope.aliyuncs.com/api/v1",
},
};
function isFamilyPresetUrl(family: AlibabaProviderFamily, value: string): boolean {
const normalized = normalizeEndpoint(value);
return ALIBABA_PROVIDER_REGION_VALUES.some(
const isCurrentPreset = ALIBABA_PROVIDER_REGION_VALUES.some(
(region) => normalizeEndpoint(ALIBABA_PROVIDER_ENDPOINTS[family][region]) === normalized
);
if (isCurrentPreset) return true;
return LEGACY_FAMILY_PRESETS[family].some((preset) => normalizeEndpoint(preset) === normalized);
}
export function isAlibabaRegionalProvider(providerId: string | null | undefined): boolean {
@@ -167,6 +209,22 @@ export function resolveAlibabaProviderMediaBaseUrl(
providerSpecificData?: unknown,
fallback = ""
): string {
const family = canonicalProviderFamily(providerId);
const data = asRecord(providerSpecificData);
const configuredBaseUrl =
typeof data.baseUrl === "string" && data.baseUrl.trim() ? data.baseUrl.trim() : "";
const mediaOverride = family ? ALIBABA_PROVIDER_MEDIA_OVERRIDES[family] : undefined;
// A custom base URL still drives media, as before — the override only replaces the
// preset-derived host.
if (
family &&
mediaOverride &&
(!configuredBaseUrl || isFamilyPresetUrl(family, configuredBaseUrl))
) {
return mediaOverride[resolveAlibabaProviderRegion(providerId, data)];
}
return stripTrailingSlashes(
resolveAlibabaProviderBaseUrl(providerId, providerSpecificData, fallback).trim()
)

View File

@@ -38,7 +38,8 @@ export const PROVIDER_ENDPOINTS = {
helixmind: "https://helixmind.online/v1/chat/completions",
glm: "https://api.z.ai/api/anthropic/v1/messages",
glmt: "https://api.z.ai/api/anthropic/v1/messages",
"bailian-coding-plan": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1/messages",
"bailian-coding-plan":
"https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1/messages",
"qwen-cloud": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
"qwen-cloud-token-plan":
"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1/chat/completions",

View File

@@ -3,7 +3,8 @@ import { gotoDashboardRoute } from "./helpers/dashboardAuth";
// #7882 replaced this provider's free-text Base URL field with a region step:
// the endpoint is now derived from the choice ("global-sg" ->
// coding-intl.dashscope.aliyuncs.com, "china-beijing" -> coding.dashscope.aliyuncs.com,
// token-plan.ap-southeast-1.maas.aliyuncs.com, "china-beijing" ->
// token-plan.cn-beijing.maas.aliyuncs.com,
// see src/shared/constants/alibabaProviderRegions.ts), so the modal persists
// providerSpecificData.region instead of a baseUrl. A per-connection base-URL
// override still exists, but it moved to Advanced in the edit-connection modal.
@@ -120,7 +121,7 @@ test.describe("Bailian Coding Plan Provider", () => {
// free-text Base URL field, which #7882 removed for this provider — an invalid
// URL is no longer reachable from this modal. Replaced with the other half of
// the region contract: the China-mainland choice must persist as typed, since
// that is what selects the coding.dashscope.aliyuncs.com endpoint.
// that is what selects the token-plan.cn-beijing.maas.aliyuncs.com endpoint.
test("region step persists the China-mainland (Beijing) choice", async ({ page }) => {
const capturedPayloads: { createProvider?: Record<string, unknown> } = {};
@@ -222,6 +223,8 @@ test.describe("Bailian Coding Plan Provider", () => {
expect(capturedPayloads.createProvider).toBeDefined();
const payload = capturedPayloads.createProvider;
expect(payload?.providerSpecificData).toBeDefined();
expect((payload?.providerSpecificData as Record<string, unknown>)?.region).toBe("china-beijing");
expect((payload?.providerSpecificData as Record<string, unknown>)?.region).toBe(
"china-beijing"
);
});
});

View File

@@ -24,8 +24,8 @@ test("Alibaba-family endpoint matrix keeps product and region boundaries distinc
"china-beijing": "https://dashscope.aliyuncs.com/compatible-mode/v1",
},
"bailian-coding-plan": {
"global-sg": "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
"china-beijing": "https://coding.dashscope.aliyuncs.com/apps/anthropic/v1",
"global-sg": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1",
"china-beijing": "https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic/v1",
},
"qwen-cloud": {
"global-sg": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
@@ -92,7 +92,7 @@ test("DefaultExecutor applies the regional endpoint to normal requests", () => {
codingPlan.buildUrl("qwen3.7-plus", true, 0, {
providerSpecificData: { region: "china-beijing" },
}),
"https://coding.dashscope.aliyuncs.com/apps/anthropic/v1/messages"
"https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic/v1/messages"
);
const qwenCloud = new DefaultExecutor("qwen-cloud");
@@ -133,7 +133,11 @@ test("provider validation probes the selected Coding Plan region", async () => {
},
});
assert.equal(result.valid, true);
assert.deepEqual(urls, ["https://coding.dashscope.aliyuncs.com/apps/anthropic/v1/messages"]);
// The stored URL is a RETIRED preset, so it must not pin the connection: the
// china-beijing selector still wins and routes to the Token Plan CN host.
assert.deepEqual(urls, [
"https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic/v1/messages",
]);
} finally {
globalThis.fetch = originalFetch;
}

View File

@@ -34,7 +34,7 @@ test("bailian-coding-plan not in OAUTH_PROVIDERS", () => {
});
// Schema validation tests for providerSpecificData.baseUrl
const VALID_BAILIAN_URL = "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1";
const VALID_BAILIAN_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1";
test("createProviderSchema accepts valid baseUrl in providerSpecificData", () => {
const validation = validateBody(createProviderSchema, {
@@ -427,7 +427,7 @@ test("validateProviderApiKey returns invalid for 401 response (bailian-coding-pl
provider: "bailian-coding-plan",
apiKey: "invalid-key",
providerSpecificData: {
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
baseUrl: "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1",
},
});
@@ -452,7 +452,7 @@ test("validateProviderApiKey returns invalid for 403 response (bailian-coding-pl
provider: "bailian-coding-plan",
apiKey: "forbidden-key",
providerSpecificData: {
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
baseUrl: "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1",
},
});
@@ -479,7 +479,7 @@ test("validateProviderApiKey returns valid for 400 response (bailian-coding-plan
provider: "bailian-coding-plan",
apiKey: "valid-key",
providerSpecificData: {
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
baseUrl: "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1",
},
});
@@ -508,7 +508,7 @@ test("validateProviderApiKey returns valid for 200 response (bailian-coding-plan
provider: "bailian-coding-plan",
apiKey: "valid-key",
providerSpecificData: {
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
baseUrl: "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1",
},
});
@@ -533,7 +533,7 @@ test("validateProviderApiKey returns invalid for 500 response (bailian-coding-pl
provider: "bailian-coding-plan",
apiKey: "bad-key",
providerSpecificData: {
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
baseUrl: "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1",
},
});
@@ -561,7 +561,7 @@ test("validateProviderApiKey avoids double /messages suffix for bailian-coding-p
provider: "bailian-coding-plan",
apiKey: "valid-key",
providerSpecificData: {
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1/messages",
baseUrl: "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1/messages",
},
});
@@ -569,7 +569,7 @@ test("validateProviderApiKey avoids double /messages suffix for bailian-coding-p
assert.equal(urls.length, 1);
assert.equal(
urls[0],
"https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1/messages",
"https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1/messages",
"Should probe exactly one /messages suffix"
);
} finally {
@@ -588,7 +588,7 @@ test("POST /api/providers validation: bailian-coding-plan with baseUrl passes sc
apiKey: "sk-placeholder-key",
name: "Test Bailian Provider",
providerSpecificData: {
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
baseUrl: "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1",
},
});
@@ -597,7 +597,7 @@ test("POST /api/providers validation: bailian-coding-plan with baseUrl passes sc
assert.equal(validation.data.provider, "bailian-coding-plan");
assert.equal(
validation.data.providerSpecificData?.baseUrl,
"https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1"
"https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1"
);
}
});

View File

@@ -0,0 +1,102 @@
/**
* bailian-coding-plan ("Alibaba Token Plan") pointed inference and validation at two
* DIFFERENT hosts.
*
* #10290 moved the open-sse registry to the Token Plan host, but the dashboard's key
* validation resolves its URL through ALIBABA_PROVIDER_REGION_ENDPOINTS, which still held
* the legacy Coding Plan host. Verified live 2026-08-18 with a valid Token Plan key:
*
* coding-intl.dashscope.aliyuncs.com → 401 invalid_api_key
* token-plan.ap-southeast-1.maas... → 429 Throttling.AllocationQuota (auth OK)
*
* validateBailianCodingPlanProvider maps 401/403 to "Invalid API key", so a perfectly
* good key was rejected at add-connection time while the very same key worked for
* inference. These tests pin the two paths together.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { REGISTRY } from "../../open-sse/config/providers/index.ts";
import { PROVIDER_ENDPOINTS } from "../../src/shared/constants/config.ts";
import { DEFAULT_PROVIDER_BASE_URLS } from "../../src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts";
import {
ALIBABA_PROVIDER_ENDPOINTS,
resolveAlibabaProviderBaseUrl,
} from "../../src/shared/constants/alibabaProviderRegions.ts";
const LEGACY_CODING_PLAN_HOST = "coding-intl.dashscope.aliyuncs.com";
test("validation resolves the same host the inference registry dispatches to", () => {
const registryBaseUrl = REGISTRY["bailian-coding-plan"].baseUrl;
const resolved = resolveAlibabaProviderBaseUrl("bailian-coding-plan", {
region: "global-sg",
});
assert.equal(
resolved,
registryBaseUrl,
"the dashboard would validate the key against a different host than inference uses"
);
});
test("no default endpoint still points at the Coding Plan host", () => {
// The catalog entry is a TOKEN Plan; Coding Plan keys are a different product and the
// legacy host rejects Token Plan keys outright. Compare parsed hostnames, not URL
// substrings (CodeQL js/incomplete-url-substring-sanitization).
assert.notEqual(
new URL(PROVIDER_ENDPOINTS["bailian-coding-plan"]).hostname,
LEGACY_CODING_PLAN_HOST,
"PROVIDER_ENDPOINTS still defaults to the legacy Coding Plan host"
);
assert.notEqual(
new URL(DEFAULT_PROVIDER_BASE_URLS["bailian-coding-plan"]).hostname,
LEGACY_CODING_PLAN_HOST,
"the dashboard base-URL placeholder still shows the legacy Coding Plan host"
);
for (const region of ["global-sg", "china-beijing"] as const) {
assert.notEqual(
new URL(ALIBABA_PROVIDER_ENDPOINTS["bailian-coding-plan"][region]).hostname,
LEGACY_CODING_PLAN_HOST,
`region ${region} still maps to the legacy Coding Plan host`
);
}
});
test("both regions keep the Anthropic-compatible path the claude format requires", () => {
// format: "claude" + chatPath "/messages" — a compatible-mode URL here would 404.
for (const region of ["global-sg", "china-beijing"] as const) {
assert.ok(
ALIBABA_PROVIDER_ENDPOINTS["bailian-coding-plan"][region].endsWith("/apps/anthropic/v1"),
`region ${region} must keep the /apps/anthropic/v1 root`
);
}
});
test("a saved legacy preset URL still follows the region selector", () => {
// Migration guard: connections created before the fix carry the legacy host in
// providerSpecificData.baseUrl. isFamilyPresetUrl must keep recognizing it as a
// preset — otherwise it is treated as a deliberate custom URL and the connection
// stays pinned to the host that rejects its key, with no way out but manual editing.
const legacyPreset = "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1";
assert.equal(
resolveAlibabaProviderBaseUrl("bailian-coding-plan", {
region: "global-sg",
baseUrl: legacyPreset,
}),
ALIBABA_PROVIDER_ENDPOINTS["bailian-coding-plan"]["global-sg"],
"a stored legacy preset must not pin the connection to the dead host"
);
});
test("a genuinely custom base URL still wins over the region preset", () => {
const custom = "https://my-gateway.internal/apps/anthropic/v1";
assert.equal(
resolveAlibabaProviderBaseUrl("bailian-coding-plan", {
region: "global-sg",
baseUrl: custom,
}),
custom
);
});

View File

@@ -220,10 +220,10 @@ test("DefaultExecutor.buildUrl normalizes configurable chat-openai-compat base U
assert.equal(
bailian.buildUrl("qwen3-coder-plus", true, 0, {
providerSpecificData: {
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
baseUrl: "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1",
},
}),
"https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1/messages"
"https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1/messages"
);
assert.equal(
heroku.buildUrl("claude-4-sonnet", true, 0, {

View File

@@ -1451,7 +1451,9 @@ test("specialty validators cover remaining status branches for Deepgram, Assembl
if (target.match(/inworld/i)) {
throw new Error("inworld offline");
}
if (target.match(/dashscope\.aliyuncs\.com/i)) {
// Alibaba-family hosts: dashscope.aliyuncs.com (pay-as-you-go / AIGC) and
// *.maas.aliyuncs.com (Token Plan).
if (target.match(/(?:dashscope|maas)\.aliyuncs\.com/i)) {
return new Response(JSON.stringify({ error: "server" }), { status: 500 });
}
if (target.match(/longcat/i)) {
@@ -1468,7 +1470,7 @@ test("specialty validators cover remaining status branches for Deepgram, Assembl
provider: "bailian-coding-plan",
apiKey: "bailian-key",
providerSpecificData: {
baseUrl: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1/messages",
baseUrl: "https://token-plan.ap-southeast-1.maas.aliyuncs.com/apps/anthropic/v1/messages",
},
});
const longcatInvalid = await validateProviderApiKey({ provider: "longcat", apiKey: "lc-key" });