Compare commits

...

1 Commits

Author SHA1 Message Date
diegosouzapw
804a3788f8 fix(providers): modal.com validation returns clear error when Base URL is missing (#9102)
Modal (modal.com) is bring-your-own-deploy and requires a Base URL pointing to the
user's OpenAI-compatible Modal app. The connect-connection form labels the Base URL
override field as Optional, but the modal validator does not handle the empty case:
when no Base URL is set it passes normalizeBaseUrl('') into validateOpenAILikeProvider,
which builds an empty probe URL and trips parseOutboundUrl, leaking the raw guard
message 'Invalid outbound URL: '.

Fix: guard the empty/whitespace baseUrl case in the modal specialty validator and
return a clear, actionable error message explaining that a Base URL is required.
Add a regression test asserting the fix.
2026-08-04 06:09:33 -03:00
3 changed files with 50 additions and 4 deletions

View File

@@ -0,0 +1 @@
- fix(providers): modal.com validation returns clear error when Base URL is missing, instead of leaking "Invalid outbound URL" (#9102)

View File

@@ -211,15 +211,30 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
oci: validateOciProvider,
sap: validateSapProvider,
bedrock: validateBedrockProvider,
modal: ({ apiKey, providerSpecificData }: any) =>
validateOpenAILikeProvider({
modal: ({ apiKey, providerSpecificData }: any) => {
// Modal is bring-your-own-deploy — it requires a Base URL pointing to the user's
// OpenAI-compatible Modal app. Without it, validateOpenAILikeProvider would build an
// empty probe URL and trip parseOutboundUrl with a raw guard error ("Invalid outbound
// URL: "). Surface an actionable message instead. See #9102.
const baseUrl = (providerSpecificData?.baseUrl || "").trim();
if (!baseUrl) {
return {
valid: false,
error:
"Modal requires a Base URL pointing to your OpenAI-compatible Modal app " +
"(e.g. https://<workspace>--<app>.modal.run/v1). " +
"Fill in the \"Base URL override\" field.",
};
}
return validateOpenAILikeProvider({
provider: "modal",
apiKey,
providerSpecificData,
baseUrl: normalizeBaseUrl(providerSpecificData?.baseUrl || ""),
baseUrl: normalizeBaseUrl(baseUrl),
modelId: MODAL_DEFAULT_VALIDATION_MODEL_ID,
isLocal,
}),
});
},
"nous-research": validateNousResearchProvider,
poe: validatePoeProvider,
clarifai: validateClarifaiProvider,

View File

@@ -0,0 +1,30 @@
import test from "node:test";
import assert from "node:assert/strict";
const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts");
test("modal validation without baseUrl returns clear actionable error (not Invalid outbound URL)", async () => {
// Ensure no actual fetch ever happens — the bug is a pre-fetch URL parse failure
globalThis.fetch = async (_url: RequestInfo | URL, _init?: RequestInit) => {
throw new Error("unexpected fetch: validation should fail before any network request");
};
const result = await validateProviderApiKey({
provider: "modal",
apiKey: "ak-test:as-test",
providerSpecificData: {},
});
// The bug: when baseUrl is empty, validateOpenAILikeProvider gets an empty URL,
// parseOutboundUrl throws "Invalid outbound URL: " — a raw guard message.
// The fix must return a clear actionable message mentioning Base URL.
const errorMsg = result.error || "";
assert.ok(
!errorMsg.includes("Invalid outbound URL"),
`bug: leaked raw guard message -> ${JSON.stringify(errorMsg)}`
);
assert.ok(
errorMsg.toLowerCase().includes("base url") || errorMsg.toLowerCase().includes("base"),
`expected error to mention Base URL, got: ${JSON.stringify(errorMsg)}`
);
});