different-ai/openwork · error

External URL protocol "${parsed.protocol}" is not allowed.

Error message

External URL protocol "${parsed.protocol}" is not allowed.

What it means

After the URL parses, assertDesktopWebUrl enforces an allow-list of protocols: only http: and https: may be opened externally. Anything else — file:, javascript:, data:, custom schemes — is rejected with a message naming the offending protocol, blocking protocol-handler abuse (e.g. opening local files or arbitrary app handlers via crafted links).

Source

Thrown at apps/app/src/app/lib/desktop.ts:478

  }
  return desktopFetchThroughMain(input, init, {
    agentContextDiagnosticsDeadlineAtMs: deadlineAtMs,
  });
}

// ---------------------------------------------------------------------------
// Convenience wrappers
// ---------------------------------------------------------------------------

export function assertDesktopWebUrl(url: string): string {
  let parsed: URL;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error("Only valid web links can be opened externally.");
  }
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
    throw new Error(`External URL protocol "${parsed.protocol}" is not allowed.`);
  }
  return parsed.toString();
}

export async function openDesktopUrl(url: string): Promise<void> {
  const safeUrl = assertDesktopWebUrl(url);
  const openExternal = window.__OPENWORK_ELECTRON__?.shell?.openExternal;
  if (openExternal) {
    const result = await openExternal(safeUrl);
    if (result && result.ok === false) {
      throw new Error(result.error ?? "Failed to open browser");
    }
    return;
  }
  if (typeof window !== "undefined") {
    window.open(safeUrl, "_blank", "noopener,noreferrer");
  }
}

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Convert non-web inputs to a permitted form first: local files should use openDesktopPath/revealDesktopItemInDir, not openDesktopUrl.
  2. Normalize the scheme: if the target is meant to be a website but uses a wrong scheme, rewrite it to https:// before calling.
  3. If a custom scheme genuinely must open, add an explicit allow-listed code path (e.g. shell.openExternal via the Electron bridge) rather than bypassing the guard silently.
  4. Sanitize dynamic link sources (chat messages, imported configs) to reject or rewrite non-http(s) URLs at ingestion.

Example fix

// before
await openDesktopUrl("file:///tmp/report.pdf");

// after
await openDesktopPath("/tmp/report.pdf"); // opens/locally reveals instead
Defensive patterns

Strategy: validation

Validate before calling

const parsed = new URL(url);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
  throw new Error(`Refusing to open ${parsed.protocol} link externally`);
}

Type guard

function isWebUrl(u: URL): boolean {
  return u.protocol === "http:" || u.protocol === "https:";
}

Try / catch

try {
  await openDesktopUrl(link);
} catch (err) {
  if (err instanceof Error && err.message.includes("is not allowed")) {
    showToast("Only web (http/https) links can be opened in the browser");
  } else { throw err; }
}

Prevention

When it happens

Trigger: openDesktopUrl / safeUrl / waitForManagedMcpAuthorization receiving a URL whose scheme is not http/https, e.g. `file:///Users/x/doc.pdf`, `javascript:void(0)`, `data:text/html,...`, or a custom app scheme like `slack://` or `vscode://` passed to the external opener.

Common situations: Attempting to reveal a local file path via openDesktopUrl instead of openDesktopPath; rendering attacker-influenced link content (markdown/chat) and opening it directly; passing internal app schemes or workspace:// URIs that the desktop bridge does not allow; tests constructing `about:blank` or `blob:` URLs.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/76ad5a868dd7184a. Report an issue: GitHub.