can1357/oh-my-pi · error · Error

collab.webUrl must use https:// unless it targets localhost

Error message

collab.webUrl must use https:// unless it targets localhost

What it means

The web link base must be encrypted in production: normalizeCollabWebBaseUrl allows plain http:// only when the hostname is localhost, 127.0.0.1, or ::1. An http:// URL aimed at any other host is rejected so room secrets (carried in the link fragment) are never sent over an unencrypted connection to a remote web UI.

Source

Thrown at packages/coding-agent/src/collab/protocol.ts:227

	if (!explicitWebUrl) {
		const normalized = normalizeRelayOrigin(relayUrl);
		if ("error" in normalized) throw new Error(normalized.error);
		return normalized.origin.startsWith("wss://")
			? `https://${normalized.origin.slice("wss://".length)}`
			: `http://${normalized.origin.slice("ws://".length)}`;
	}

	let url: URL;
	try {
		url = new URL(explicitWebUrl);
	} catch {
		throw new Error("collab.webUrl must start with http:// or https://");
	}
	if (url.protocol !== "http:" && url.protocol !== "https:") {
		throw new Error("collab.webUrl must start with http:// or https://");
	}
	if (url.protocol === "http:" && !isLocalHostname(url.hostname)) {
		throw new Error("collab.webUrl must use https:// unless it targets localhost");
	}
	if (url.search || url.hash) {
		throw new Error("collab.webUrl must not include a query string or fragment");
	}
	const path = url.pathname.replace(/\/+$/, "");
	return `${url.origin}${path}`;
}

/**
 * Render the browser deep link. The browser UI may be hosted separately from
 * the relay; the fragment always carries the relay-specific collab link, so
 * room secrets stay out of HTTP path and query bytes.
 */
export function formatCollabWebLink(
	relayUrl: string,
	roomId: string,
	key: Uint8Array,
	writeToken?: Uint8Array,

View on GitHub (pinned to 9690622007)

Solutions

  1. Serve the web UI over https:// (any TLS-terminating proxy or the host's own certificate) and update webUrl accordingly.
  2. For pure local testing, use http://localhost:PORT (or 127.0.0.1/::1), which is the allowed exception.
  3. If testing from other machines on the LAN, set up a self-signed or real certificate and use https even internally.
  4. Use the hostname literally 'localhost' in the URL, not a container/hostname alias, when you intend the local exception.

Example fix

// before
webUrl: "http://collab.example.com"
// after
webUrl: "https://collab.example.com"
Defensive patterns

Strategy: validation

Validate before calling

const u = new URL(webUrl);
const local = ["localhost", "127.0.0.1", "::1"].includes(u.hostname);
if (u.protocol === "http:" && !local) throw new Error("webUrl must use https:// (http only allowed for localhost)");
formatCollabWebLink(relayUrl, roomId, key, token, webUrl);

Try / catch

try {
  const webLink = formatCollabWebLink(relayUrl, roomId, key, token, webUrl);
} catch (err) {
  ui.showError(`Insecure webUrl: ${(err as Error).message}`);
}

Prevention

When it happens

Trigger: Calling formatCollabWebLink with webUrl like 'http://collab.example.com' or 'http://192.168.1.10:3000' — valid http(s) URLs, but plain http to a non-local hostname.

Common situations: Self-hosting the web UI behind a LAN IP or internal DNS name without TLS; local testing against a container name like http://web:3000 (not recognized as local); forgetting to front the UI with TLS before sharing links.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/6f6523ab7468816f. Report an issue: GitHub.