decolua/9router · error

Invalid baseUrl protocol: ${parsed.protocol}

Error message

Invalid baseUrl protocol: ${parsed.protocol}

What it means

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.

Source

Thrown at open-sse/handlers/search/callers.js:89

 * rejected via assertPublicUrl). The provider's own configured baseUrl is
 * trusted as-is (admin-controlled).
 *
 * @param {SearchProviderConfig} config
 * @param {SearchRequestParams} params
 * @returns {string}
 */
export function resolveBaseUrl(config, params) {
  const override = getProviderSetting(params, "baseUrl");
  if (override) {
    // SSRF guard: client-supplied base URLs must be public http(s) only.
    let parsed;
    try {
      parsed = new URL(override);
    } catch {
      throw new Error(`Invalid baseUrl: ${override}`);
    }
    if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
      throw new Error(`Invalid baseUrl protocol: ${parsed.protocol}`);
    }
    assertPublicUrl(override);
  }
  return (override || config.baseUrl).replace(/\/+$/, "");
}

/**
 * Convert offset+maxResults to 1-indexed page number.
 * @param {number|undefined} offset
 * @param {number} maxResults
 * @returns {number|undefined}
 */
export function toPageNumber(offset, maxResults) {
  if (typeof offset !== "number" || offset <= 0 || maxResults <= 0) return undefined;
  return Math.floor(offset / maxResults) + 1;
}

// ── Provider Request Builders ───────────────────────────────────────────

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Prefix the override with an explicit http:// or https:// scheme ("https://myproxy:8443")
  2. Check what protocol the URL actually parses to: `new URL(value).protocol` — a bare "host:port" string yields "host:" and must be fixed
  3. Remove the baseUrl override entirely if the provider's default configured baseUrl is what you want
  4. If this appears in logs unexpectedly, treat it as a possible SSRF probe and audit who can set providerOptions on search requests

Example fix

// before — parses as protocol "myproxy:" and throws
providerOptions: { baseUrl: "myproxy:8443" }
// after
providerOptions: { baseUrl: "https://myproxy:8443" }
Defensive patterns

Strategy: validation

Validate before calling

function assertHttpScheme(v) {
  const u = new URL(v); // caller already knows it parses
  if (u.protocol !== "http:" && u.protocol !== "https:") {
    throw new Error(`baseUrl must start with http:// or https:// (parsed protocol: ${u.protocol})`);
  }
}

Type guard

function isPlainHttpUrl(v) {
  try { const u = new URL(v); return (u.protocol === "http:" || u.protocol === "https:") && u.hostname.length > 0; } catch { return false; }
}

Try / catch

try {
  const result = await search({ provider: "tavily", providerOptions: { baseUrl: override } });
} catch (err) {
  if (String(err.message).startsWith("Invalid baseUrl protocol")) {
    // reject the override: scheme is not http(s) — e.g. bare "host:port" became a custom scheme
    // fix to https://host:port and retry
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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://".

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/8867c687332792a5. Report an issue: GitHub.