Skip to content

Commit ba074f5

Browse files
committed
fix(security): validate base_url / console_gateway_url as real http(s) URLs
The config file accepted any value that merely starts with "http" (so even "httpfoo://evil" passed) for base_url and console_gateway_url — origins the client sends the Bearer token to. Validate them with `new URL()` and an http:/https: protocol check instead, rejecting malformed values. Valid http(s) URLs (including custom proxies and local http) are unaffected. https://claude.ai/code/session_017ZGQCjwNQF5Pz96gLUnnG1
1 parent bb9f941 commit ba074f5

2 files changed

Lines changed: 31 additions & 3 deletions

File tree

packages/core/src/config/schema.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,21 @@ export interface ConfigFile {
3838
const VALID_REGIONS = new Set<string>(["cn", "us", "intl"]);
3939
const VALID_OUTPUTS = new Set<string>(["text", "json"]);
4040

41+
/**
42+
* A syntactically valid absolute http(s) URL. Used to validate `base_url` and
43+
* `console_gateway_url` from the config file: the credential-bearing client
44+
* sends the Bearer token to these origins, so a bare `startsWith("http")` check
45+
* (which also accepts e.g. "httpfoo://…") is too loose.
46+
*/
47+
function isHttpUrl(value: string): boolean {
48+
try {
49+
const u = new URL(value);
50+
return u.protocol === "http:" || u.protocol === "https:";
51+
} catch {
52+
return false;
53+
}
54+
}
55+
4156
export function parseConfigFile(raw: unknown): ConfigFile {
4257
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
4358
const obj = raw as Record<string, unknown>;
@@ -50,8 +65,7 @@ export function parseConfigFile(raw: unknown): ConfigFile {
5065
out.access_token = obj.accessToken;
5166
if (typeof obj.region === "string" && VALID_REGIONS.has(obj.region))
5267
out.region = obj.region as Region;
53-
if (typeof obj.base_url === "string" && obj.base_url.startsWith("http"))
54-
out.base_url = obj.base_url;
68+
if (typeof obj.base_url === "string" && isHttpUrl(obj.base_url)) out.base_url = obj.base_url;
5569
if (typeof obj.output === "string" && VALID_OUTPUTS.has(obj.output))
5670
out.output = obj.output as ConfigFile["output"];
5771
if (typeof obj.output_dir === "string" && obj.output_dir.length > 0)
@@ -73,7 +87,7 @@ export function parseConfigFile(raw: unknown): ConfigFile {
7387
out.access_key_secret = obj.access_key_secret;
7488
if (typeof obj.workspace_id === "string" && obj.workspace_id.length > 0)
7589
out.workspace_id = obj.workspace_id;
76-
if (typeof obj.console_gateway_url === "string" && obj.console_gateway_url.startsWith("http"))
90+
if (typeof obj.console_gateway_url === "string" && isHttpUrl(obj.console_gateway_url))
7791
out.console_gateway_url = obj.console_gateway_url;
7892
if (typeof obj.telemetry === "boolean") out.telemetry = obj.telemetry;
7993

packages/core/tests/index.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { expect, test } from "vite-plus/test";
22
import type { Config } from "../src/index.ts";
33
import { BailianError, ExitCode, McpClient, mapApiError, request } from "../src/index.ts";
4+
import { parseConfigFile } from "../src/config/schema.ts";
45

56
function testConfig(overrides: Partial<Config> = {}): Config {
67
return {
@@ -173,3 +174,16 @@ test("McpClient uses injected client identity for initialize and User-Agent", as
173174
params: { clientInfo: { name: "test-client", version: "9.8.7" } },
174175
});
175176
});
177+
178+
test("parseConfigFile accepts only well-formed http(s) base_url / console_gateway_url", () => {
179+
expect(parseConfigFile({ base_url: "https://dashscope.aliyuncs.com" }).base_url).toBe(
180+
"https://dashscope.aliyuncs.com",
181+
);
182+
expect(parseConfigFile({ base_url: "http://localhost:8080" }).base_url).toBe(
183+
"http://localhost:8080",
184+
);
185+
// Previously accepted because the value merely "starts with http".
186+
expect(parseConfigFile({ base_url: "httpfoo://evil" }).base_url).toBeUndefined();
187+
expect(parseConfigFile({ base_url: "not a url" }).base_url).toBeUndefined();
188+
expect(parseConfigFile({ console_gateway_url: "ftp://x" }).console_gateway_url).toBeUndefined();
189+
});

0 commit comments

Comments
 (0)