{"record":{"id":"2c7da822852c65e6","repo":"farion1231/cc-switch","slug":"unsupported-url-scheme","errorCode":null,"errorMessage":"Unsupported URL scheme","messagePattern":"Unsupported URL scheme","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/lib/api/settings.ts","lineNumber":220,"sourceCode":"    return await invoke(\"s3_sync_fetch_remote_info\");\n  },\n\n  async syncCurrentProvidersLive(): Promise<void> {\n    const result = (await invoke(\"sync_current_providers_live\")) as {\n      success?: boolean;\n      message?: string;\n    };\n    if (!result?.success) {\n      throw new Error(result?.message || \"Sync current providers failed\");\n    }\n  },\n\n  async openExternal(url: string): Promise<void> {\n    try {\n      const u = new URL(url);\n      const scheme = u.protocol.replace(\":\", \"\").toLowerCase();\n      if (scheme !== \"http\" && scheme !== \"https\") {\n        throw new Error(\"Unsupported URL scheme\");\n      }\n    } catch {\n      throw new Error(\"Invalid URL\");\n    }\n    await invoke(\"open_external\", { url });\n  },\n\n  async setAutoLaunch(enabled: boolean): Promise<boolean> {\n    return await invoke(\"set_auto_launch\", { enabled });\n  },\n\n  async getAutoLaunchStatus(): Promise<boolean> {\n    return await invoke(\"get_auto_launch_status\");\n  },\n\n  async getToolVersions(\n    tools?: string[],\n    wslShellByTool?: Record<","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/farion1231/cc-switch/blob/a2e22f330273a5b6ffa87cb8b82b624601bac562/src/lib/api/settings.ts#L202-L238","documentation":"Intended to be thrown by settings.openExternal(url) when the URL parses but its scheme is not http/https (case-insensitive), guarding the Tauri open_external shell call against file:, javascript:, and custom protocols. Quirk: the throw happens inside the same try block whose catch rewrites every failure to 'Invalid URL', so callers currently observe 'Invalid URL' for non-http schemes - this exact message is unreachable as written.","triggerScenarios":"openExternal('file:///etc/hosts'), openExternal('javascript:alert(1)'), openExternal('slack://channel'), openExternal('mailto:a@b.c') - any parseable URL whose protocol is neither http nor https (observed by the caller as 'Invalid URL').","commonSituations":"Clicking deep links (zoom://, vscode://, msteams://); a user-configurable link field holding a custom protocol; security hardening that forbids file:/javascript: URLs from reaching the OS shell.","solutions":["Restrict UI anchors to absolute http/https URLs before calling openExternal","If distinct messages matter, hoist the scheme check out of the try block so 'Unsupported URL scheme' survives the catch","Render custom-protocol links as plain text, or route them through an explicit user-consent dialog"],"exampleFix":"// before\ntry {\n  const u = new URL(url);\n  const scheme = u.protocol.replace(\":\", \"\").toLowerCase();\n  if (scheme !== \"http\" && scheme !== \"https\") {\n    throw new Error(\"Unsupported URL scheme\"); // swallowed by catch below\n  }\n} catch {\n  throw new Error(\"Invalid URL\");\n}\n\n// after\nlet u: URL;\ntry {\n  u = new URL(url);\n} catch {\n  throw new Error(\"Invalid URL\");\n}\nconst scheme = u.protocol.replace(\":\", \"\").toLowerCase();\nif (scheme !== \"http\" && scheme !== \"https\") {\n  throw new Error(\"Unsupported URL scheme\");\n}","handlingStrategy":"validation","validationCode":"function isSafeExternalUrl(url: string): boolean {\n  try {\n    const { protocol } = new URL(url);\n    return protocol === \"http:\" || protocol === \"https:\";\n  } catch {\n    return false;\n  }\n}\n\nif (isSafeExternalUrl(href)) {\n  await settingsApi.openExternal(href);\n}","typeGuard":"type HttpUrl = `http://${string}` | `https://${string}`;\n\nfunction isHttpUrl(v: string): v is HttpUrl {\n  return /^https?:\\/\\/\\S+$/.test(v);\n}","tryCatchPattern":"// The scheme throw is rewritten to 'Invalid URL' by the shared catch,\n// so match that message for both malformed and non-http(s) cases.\ntry {\n  await settingsApi.openExternal(url);\n} catch (e) {\n  if (e instanceof Error && e.message === \"Invalid URL\") {\n    toast(\"Only http:// and https:// links can be opened\");\n  } else {\n    throw e;\n  }\n}","preventionTips":["Whitelist http/https before rendering clickable anchors","Never pass user-supplied custom-protocol URLs to openExternal","Keep the scheme check outside the URL-parse try block when distinct error messages matter"],"tags":["url","security","validation","tauri"],"backgroundTag":null,"analyzedSha":"a2e22f330273a5b6ffa87cb8b82b624601bac562","analyzedAt":"2026-08-16T03:46:07.889Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}