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

  1. Change the URL to use http:// or https:// (e.g. https://example.com).
  2. If you intend to open a CCR plugin, use the exact form ccr://plugin/<plugin-id> — the hostname must be 'plugin'.
  3. Sanitize/validate URLs on the client side before sending them to the management server.
  4. 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

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


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/f4bba1f6e56fdc8e. Report an issue: GitHub.