{"record":{"id":"3defc5905a8ac261","repo":"stablyai/orca","slug":"localhost-label-target-is-not-a-valid-url","errorCode":null,"errorMessage":"Localhost label target is not a valid URL.","messagePattern":"Localhost label target is not a valid URL\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ipc/localhost-worktree-labels.ts","lineNumber":35,"sourceCode":"  ipcMain.handle(\n    'localhostWorktreeLabels:register',\n    async (_event, rawArgs: unknown): Promise<LocalhostWorktreeLabelResult> => {\n      const route = parseRegisterArgs(rawArgs)\n      // Why: the proxy will forward to any host it's given, so we restrict the\n      // target to loopback or a host:port that matches a live workspace port —\n      // otherwise this IPC is an open proxy / SSRF vector.\n      await assertAllowedTarget(store, route.targetUrl)\n      return localhostWorktreeLabelProxy.registerRoute(route)\n    }\n  )\n}\n\nasync function assertAllowedTarget(store: Store, targetUrl: string): Promise<void> {\n  let parsed: URL\n  try {\n    parsed = new URL(targetUrl)\n  } catch {\n    throw new Error('Localhost label target is not a valid URL.')\n  }\n  const targetHost = normalizeLocalhostHostname(parsed.hostname)\n  if (LOOPBACK_LOCALHOST_HOSTS.has(targetHost)) {\n    return\n  }\n\n  // Why: URL drops the port for protocol defaults (e.g. http://host/ on 80),\n  // so compare against the effective port rather than the raw (empty) string.\n  const targetPort = parsed.port || (parsed.protocol === 'https:' ? '443' : '80')\n  // Why (#11161): a metadata-skipped scan drops advertisedUrl, which would\n  // silently narrow this allowlist on an EDR-hooked host.\n  const scan = await scanWorkspacePortProbes(getStoreWorkspacePortProbes(store), {\n    requireMetadata: true\n  })\n  const matches = scan.ports.some((port) => {\n    if (String(port.port) !== targetPort) {\n      return false\n    }","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ipc/localhost-worktree-labels.ts#L17-L53","documentation":"Thrown by assertAllowedTarget when `new URL(targetUrl)` throws, i.e. the targetUrl passed to register a localhost worktree label route is not a parseable URL. This is the first guard in an SSRF-prevention chain: the proxy route must target a real URL before the hostname/port allowlist is checked.","triggerScenarios":"Calling the localhost-worktree-labels register IPC with a targetUrl that is empty, missing the scheme, contains invalid characters, or is otherwise unparseable by the URL constructor. Examples: 'localhost:3000' (no scheme), 'ht!tp://x', ''.","commonSituations":"Renderer constructs the URL from a port number without prepending 'http://'. User-entered target omits the protocol. A template/string-build bug yields an empty or malformed URL.","solutions":["Construct the targetUrl with an explicit scheme by building the string \"http://\" + host + \":\" + port and passing it through new URL(...).toString().","Validate the URL parses on the caller side before invoking the register IPC.","Ensure host and port are non-empty when building the URL string."],"exampleFix":"// before\nawait ipc.call('localhostLabel:register', { targetUrl: `${host}:${port}`, ... })\n\n// after\nconst targetUrl = new URL(`http://${host}:${port}`).toString()\nawait ipc.call('localhostLabel:register', { targetUrl, ... })","handlingStrategy":"validation","validationCode":"let parsed: URL\ntry {\n  parsed = new URL(targetUrl)\n} catch {\n  throw new Error('targetUrl must be a valid URL with scheme')\n}","typeGuard":"function isParsableUrl(value: unknown): value is string {\n  if (typeof value !== 'string') return false\n  try { new URL(value); return true } catch { return false }\n}","tryCatchPattern":null,"preventionTips":["Always build targetUrl with an explicit scheme via new URL(`http://${host}:${port}`).","Validate the URL parses on the caller side before registering.","Never pass a bare host:port string without a protocol."],"tags":["security","ssrf","validation","url","localhost","ipc"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}