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
- 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
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
- 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
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
- Invalid baseUrl: ${override}
- Blocked URL: internal host
- err.message (SSRF guard: blocked internal/private/metadata U
- Google Programmable Search requires both apiKey and cx
- Linkup Search requires an API key
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/8867c687332792a5.
Report an issue: GitHub.