{"record":{"id":"8867c687332792a5","repo":"decolua/9router","slug":"invalid-baseurl-protocol-parsed-protocol","errorCode":null,"errorMessage":"Invalid baseUrl protocol: ${parsed.protocol}","messagePattern":"Invalid baseUrl protocol: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"open-sse/handlers/search/callers.js","lineNumber":89,"sourceCode":" * rejected via assertPublicUrl). The provider's own configured baseUrl is\n * trusted as-is (admin-controlled).\n *\n * @param {SearchProviderConfig} config\n * @param {SearchRequestParams} params\n * @returns {string}\n */\nexport function resolveBaseUrl(config, params) {\n  const override = getProviderSetting(params, \"baseUrl\");\n  if (override) {\n    // SSRF guard: client-supplied base URLs must be public http(s) only.\n    let parsed;\n    try {\n      parsed = new URL(override);\n    } catch {\n      throw new Error(`Invalid baseUrl: ${override}`);\n    }\n    if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n      throw new Error(`Invalid baseUrl protocol: ${parsed.protocol}`);\n    }\n    assertPublicUrl(override);\n  }\n  return (override || config.baseUrl).replace(/\\/+$/, \"\");\n}\n\n/**\n * Convert offset+maxResults to 1-indexed page number.\n * @param {number|undefined} offset\n * @param {number} maxResults\n * @returns {number|undefined}\n */\nexport function toPageNumber(offset, maxResults) {\n  if (typeof offset !== \"number\" || offset <= 0 || maxResults <= 0) return undefined;\n  return Math.floor(offset / maxResults) + 1;\n}\n\n// ── Provider Request Builders ───────────────────────────────────────────","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/search/callers.js#L71-L107","documentation":"resolveBaseUrl parsed the client-supplied baseUrl override successfully, but its protocol is not http: or https:. The SSRF guard only permits public HTTP(S) endpoints, so schemes like file:, ftp:, ws:, data:, or an accidentally URL-parsed custom scheme are rejected before any request is made.","triggerScenarios":"providerOptions.baseUrl or providerSpecificData.baseUrl on any of the six search builders resolves to a URL whose parsed.protocol is neither \"http:\" nor \"https:\" — e.g. \"ftp://proxy/x\", \"file:///etc/passwd\", or a bare host like \"myproxy:8443\" where `new URL` treats \"myproxy:\" as the protocol.","commonSituations":"Bare host:port values silently become a bogus custom protocol (the most common case); someone attempts file:/data: URLs probing the SSRF surface; ws:// proxy configs copied from WebSocket client code; typo'd scheme like \"htps://\".","solutions":["Prefix the override with an explicit http:// or https:// scheme (\"https://myproxy:8443\")","Check what protocol the URL actually parses to: `new URL(value).protocol` — a bare \"host:port\" string yields \"host:\" and must be fixed","Remove the baseUrl override entirely if the provider's default configured baseUrl is what you want","If this appears in logs unexpectedly, treat it as a possible SSRF probe and audit who can set providerOptions on search requests"],"exampleFix":"// before — parses as protocol \"myproxy:\" and throws\nproviderOptions: { baseUrl: \"myproxy:8443\" }\n// after\nproviderOptions: { baseUrl: \"https://myproxy:8443\" }","handlingStrategy":"validation","validationCode":"function assertHttpScheme(v) {\n  const u = new URL(v); // caller already knows it parses\n  if (u.protocol !== \"http:\" && u.protocol !== \"https:\") {\n    throw new Error(`baseUrl must start with http:// or https:// (parsed protocol: ${u.protocol})`);\n  }\n}","typeGuard":"function isPlainHttpUrl(v) {\n  try { const u = new URL(v); return (u.protocol === \"http:\" || u.protocol === \"https:\") && u.hostname.length > 0; } catch { return false; }\n}","tryCatchPattern":"try {\n  const result = await search({ provider: \"tavily\", providerOptions: { baseUrl: override } });\n} catch (err) {\n  if (String(err.message).startsWith(\"Invalid baseUrl protocol\")) {\n    // reject the override: scheme is not http(s) — e.g. bare \"host:port\" became a custom scheme\n    // fix to https://host:port and retry\n  } else {\n    throw err;\n  }\n}","preventionTips":["Never pass bare \"host:port\" strings — new URL parses the host as the protocol","Whitelist schemes at config load time, not request time","Treat protocol violations in logs as potential SSRF probing and audit client inputs"],"tags":["ssrf","url-validation","search","protocol"],"backgroundTag":"invalid-url-protocol","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}