musistudio/claude-code-router · error · Error
Only http, https, and CCR plugin URLs can be opened.
Error message
Only http, https, and CCR plugin URLs can be opened.
What it means
Thrown by the management server's URL-opening endpoint when the submitted URL's protocol is not http:, https:, or ccr: with a hostname of 'plugin'. It is a security guard preventing the app from opening arbitrary schemes (e.g. file:, javascript:, or custom OS handlers) that could escape the sandbox or launch arbitrary programs. Only whitelisted web URLs and the internal CCR plugin deep-link format are accepted.
Source
Thrown at packages/core/src/web/management-server.ts:1471
function normalizeExternalTarget(target: unknown): string | undefined {
const trimmed = typeof target === "string" ? target.trim() : "";
if (!trimmed || trimmed === "about:blank") {
return undefined;
}
let url: URL;
try {
url = new URL(trimmed);
} catch {
throw new Error("External URL must be a valid absolute URL.");
}
if (url.protocol === "http:" || url.protocol === "https:") {
return url.toString();
}
if (url.protocol === "ccr:" && url.hostname.toLowerCase() === "plugin") {
return url.toString();
}
throw new Error("Only http, https, and CCR plugin URLs can be opened.");
}
function execDetached(command: string, args: string[]): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
detached: true,
stdio: "ignore",
windowsHide: true
});
child.once("error", reject);
child.once("spawn", () => {
child.unref();
resolve();
});
});
}
function contentTypeForFile(file: string): string {View on GitHub (pinned to 99f24806c6)
Solutions
- Change the URL to use http:// or https:// (e.g. https://example.com).
- If you intend to open a CCR plugin, use the exact form ccr://plugin/<plugin-id> — the hostname must be 'plugin'.
- Sanitize/validate URLs on the client side before sending them to the management server.
- Do not attempt to open file:, ftp:, or custom OS schemes through this endpoint; use the appropriate file API instead.
Example fix
// before
await client.openUrl("file:///home/user/report.pdf");
// after
await client.openUrl("https://example.com/report.pdf"); Defensive patterns
Strategy: validation
Validate before calling
function isOpenableUrl(value: string): boolean {
try {
const u = new URL(value);
return u.protocol === "http:" || u.protocol === "https:" ||
(u.protocol === "ccr:" && u.hostname.toLowerCase() === "plugin");
} catch {
return false;
}
}
if (!isOpenableUrl(url)) throw new Error(`Refusing to send non-openable URL: ${url}`);
await client.openUrl(url); Type guard
function isOpenableUrl(value: string): value is `${"http" | "https"}://${string}` { /* see validationCode */ } Try / catch
try { await client.openUrl(url); } catch (e) { if (e instanceof Error && e.message.includes("Only http, https, and CCR plugin URLs")) { /* fix scheme, notify user */ } throw e; } Prevention
- Normalize user-supplied URLs through new URL() before sending
- Whitelist protocols client-side
- Never construct ccr: links with a hostname other than 'plugin'
When it happens
Trigger: Calling the management server's open-URL API with a URL whose protocol is anything other than http:, https:, or ccr://plugin/... — e.g. 'file:///etc/passwd', 'ftp://host', 'ccr://settings', or a malformed string that URL-parses to an unexpected scheme.
Common situations: A client passes a user-supplied or clipboard-copied URL without sanitizing; a deep link built with the wrong ccr: hostname; attempts to open local file resources through a web-oriented endpoint.
Related errors
- Only http and https QR login URLs can be opened.
- Artifact URL contains an invalid access token.
- ${label} cannot include credentials.
- Only http and https URLs can be opened.
- Artifact origin is not the configured CCR gateway.
AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27).
Data as JSON: /api/errors/f4bba1f6e56fdc8e.
Report an issue: GitHub.