decolua/9router · error

Invalid baseUrl: ${override}

Error message

Invalid baseUrl: ${override}

What it means

resolveBaseUrl validates a client-supplied `providerOptions.baseUrl` (or `providerSpecificData.baseUrl`) override before using it for a search request. The string could not be parsed by `new URL()`, so it is not a valid absolute URL and is rejected. This is a deliberate fail-fast: malformed overrides would otherwise produce broken request URLs downstream.

Source

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

 *
 * The override is client-controlled and therefore SSRF-hardened: only public
 * http(s) URLs are accepted (internal/private/loopback/metadata addresses are
 * 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;

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Add an explicit scheme to the override: use "https://host" or "http://host:port", not a bare hostname
  2. If you didn't intend an override, remove the baseUrl key from providerOptions/providerSpecificData so the provider's configured baseUrl is used
  3. Verify the value with `new URL(value)` in Node REPL first — it must parse without throwing
  4. Remember the override must also pass the SSRF guard (public http(s) only), so loopback/private hosts will be rejected next

Example fix

// before
providerOptions: { baseUrl: "my-proxy.internal" }
// after
providerOptions: { baseUrl: "https://my-proxy.internal" }
Defensive patterns

Strategy: validation

Validate before calling

function validateBaseUrlOverride(v) {
  if (typeof v !== "string" || v.trim() === "") return null; // no override
  let u;
  try { u = new URL(v); } catch { throw new Error(`baseUrl must be an absolute URL, got: ${v}`); }
  if (u.protocol !== "http:" && u.protocol !== "https:") throw new Error(`baseUrl scheme must be http(s): ${u.protocol}`);
  return v;
}
// call before issuing the request: validateBaseUrlOverride(params.providerOptions?.baseUrl);

Type guard

function isHttpUrl(v) {
  try { const u = new URL(v); return u.protocol === "http:" || u.protocol === "https:"; } catch { return false; }
}

Try / catch

try {
  const result = await search({ provider: "serper", providerOptions: { baseUrl: override } });
} catch (err) {
  if (String(err.message).startsWith("Invalid baseUrl")) {
    // fix or drop the providerOptions.baseUrl override, then retry once
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Any search request (Serper, Brave, Perplexity, Exa, Tavily, Google PSE) whose params include providerOptions.baseUrl or providerSpecificData.baseUrl set to a string that `new URL()` throws on — e.g. "my-proxy.internal" (no scheme), "localhost:8080" (parsed as scheme "localhost:"), empty-looking garbage, or a bare host/path.

Common situations: Developers self-hosting a search proxy set baseUrl to a bare hostname without the http(s):// scheme; copied config uses a relative path like "/api/search"; typo like "http//proxy:3000" (missing colon); trailing config migrations leaving placeholder strings.

Related errors


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