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
- Add an explicit scheme to the override: use "https://host" or "http://host:port", not a bare hostname
- If you didn't intend an override, remove the baseUrl key from providerOptions/providerSpecificData so the provider's configured baseUrl is used
- Verify the value with `new URL(value)` in Node REPL first — it must parse without throwing
- 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
- Always include the https:// scheme in baseUrl overrides
- Run new URL(value) in a linter/CI check on any configured override
- Omit the baseUrl key entirely rather than passing empty or placeholder strings
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
- Invalid baseUrl protocol: ${parsed.protocol}
- Google Programmable Search requires both apiKey and cx
- Linkup Search requires an API key
- SearchAPI requires an API key
- You.com Search requires an API key
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/1f5b79909f87c9d4.
Report an issue: GitHub.