Harden custom provider endpoint validation
This commit is contained in:
1 parent
745c22ec77
commit
d85a47c40c
3 files changed
+26
-8
No files matched your search
@@ -75,12 +75,12 @@ export function parseVertexProfile(secret: string): ProviderAuthProfile {
|
||||
|
||||
export function parseOpenAiCompatProfile(secret: string): ProviderAuthProfile {
|
||||
const parsed = parseCloudSecret(secret);
|
||||
return { type: "openai_compat", baseUrl: normalizedEndpointUrl(requiredString(parsed.baseUrl, "Base URL"), "Base URL"), apiKey: requiredString(parsed.secret, "API key") };
|
||||
return { type: "openai_compat", baseUrl: normalizedHttpsUrl(requiredString(parsed.baseUrl, "Base URL"), "Base URL"), apiKey: requiredString(parsed.secret, "API key") };
|
||||
}
|
||||
|
||||
export function parseAnthropicCompatProfile(secret: string): ProviderAuthProfile {
|
||||
const parsed = parseCloudSecret(secret);
|
||||
return { type: "anthropic_compat", baseUrl: normalizedEndpointUrl(requiredString(parsed.baseUrl, "Base URL"), "Base URL"), apiKey: requiredString(parsed.secret, "API key") };
|
||||
return { type: "anthropic_compat", baseUrl: normalizedHttpsUrl(requiredString(parsed.baseUrl, "Base URL"), "Base URL"), apiKey: requiredString(parsed.secret, "API key") };
|
||||
}
|
||||
|
||||
export function requireOpenAiCompatProfile(profile: ProviderAuthProfile): Extract<ProviderAuthProfile, { type: "openai_compat" }> {
|
||||
@@ -159,11 +159,7 @@ function nonEmptySecret(secret: string, label: string): string {
|
||||
function normalizedHttpsUrl(value: string, label: string): string {
|
||||
const url = new URL(value);
|
||||
if (url.protocol !== "https:") throw new Error(`${label} must use HTTPS.`);
|
||||
return url.toString().replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function normalizedEndpointUrl(value: string, label: string): string {
|
||||
const url = new URL(value);
|
||||
if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error(`${label} must be an http(s) URL.`);
|
||||
if (url.username || url.password) throw new Error(`${label} must not include credentials.`);
|
||||
if (url.search || url.hash) throw new Error(`${label} must not include query strings or fragments.`);
|
||||
return url.toString().replace(/\/+$/, "");
|
||||
}
|
||||
@@ -85,6 +85,20 @@ describe("provider contracts", () => {
|
||||
deployment: "gpt-5.4",
|
||||
secret: "azure-key",
|
||||
}))).toThrow("Azure base URL must use HTTPS.");
|
||||
for (const providerKey of ["custom-openai", "custom-anthropic"]) {
|
||||
expect(() => providerAdapters[providerKey].parseAuthProfile(JSON.stringify({
|
||||
baseUrl: "http://api.example.com/v1",
|
||||
secret: "sk-custom",
|
||||
}))).toThrow("Base URL must use HTTPS.");
|
||||
expect(() => providerAdapters[providerKey].parseAuthProfile(JSON.stringify({
|
||||
baseUrl: "https://user:token@api.example.com/v1",
|
||||
secret: "sk-custom",
|
||||
}))).toThrow("Base URL must not include credentials.");
|
||||
expect(() => providerAdapters[providerKey].parseAuthProfile(JSON.stringify({
|
||||
baseUrl: "https://api.example.com/v1?tenant=acme",
|
||||
secret: "sk-custom",
|
||||
}))).toThrow("Base URL must not include query strings or fragments.");
|
||||
}
|
||||
});
|
||||
|
||||
// Providers with no enumerable models endpoint keep returning the curated
|
||||
|
||||
@@ -604,10 +604,12 @@ describe("provider service", () => {
|
||||
const providers = new ProviderService(connection.db);
|
||||
let orgId = "";
|
||||
let azureProviderId = "";
|
||||
let customOpenAiProviderId = "";
|
||||
|
||||
connection.transaction(() => {
|
||||
orgId = createOrganization(organization);
|
||||
azureProviderId = providerId(connection, orgId, "azure-openai");
|
||||
customOpenAiProviderId = providerId(connection, orgId, "custom-openai");
|
||||
});
|
||||
|
||||
expect(() => connection.transaction(() => providers.saveCredential({
|
||||
@@ -616,6 +618,12 @@ describe("provider service", () => {
|
||||
secret: JSON.stringify({ baseUrl: "http://example.com", deployment: "gpt-5", secret: "key" }),
|
||||
model: "gpt-5",
|
||||
}))).toThrow("Azure base URL must use HTTPS.");
|
||||
expect(() => connection.transaction(() => providers.saveCredential({
|
||||
orgId,
|
||||
providerId: customOpenAiProviderId,
|
||||
secret: JSON.stringify({ baseUrl: "http://api.example.com/v1", secret: "key" }),
|
||||
model: "gpt-5",
|
||||
}))).toThrow("Base URL must use HTTPS.");
|
||||
connection.close();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user