paperclipai/paperclip · error
devUiUrl must use http or https protocol
Error message
devUiUrl must use http or https protocol
What it means
Returned as HTTP 400 by the plugin UI dev proxy (server/src/routes/plugin-ui-static.ts:341). After constructing targetUrl = new URL(rawFilePath, devUiUrl), the route only proxies when the resulting protocol is http or https. Any other scheme (file:, ftp:, or a scheme-looking host like 'localhost:5173' which the URL parser reads as protocol 'localhost:') is rejected as SSRF protection.
Source
Thrown at server/src/routes/plugin-ui-static.ts:341
} catch {
res.status(400).json({ error: "Invalid file path" });
return;
}
if (
decodedPath.includes("://") ||
decodedPath.startsWith("//") ||
decodedPath.startsWith("\\\\")
) {
res.status(400).json({ error: "Invalid file path" });
return;
}
// Proxy the request to the dev server
const targetUrl = new URL(rawFilePath, devUiUrl.endsWith("/") ? devUiUrl : devUiUrl + "/");
// SSRF protection: only allow http/https and localhost targets for dev proxy
if (targetUrl.protocol !== "http:" && targetUrl.protocol !== "https:") {
res.status(400).json({ error: "devUiUrl must use http or https protocol" });
return;
}
// Dev proxy is restricted to loopback addresses only.
// Validate the *constructed* targetUrl hostname (not the base) to
// catch any path-based override that slipped past the checks above.
const devHost = targetUrl.hostname;
const isLoopback =
devHost === "localhost" ||
devHost === "127.0.0.1" ||
devHost === "::1" ||
devHost === "[::1]";
if (!isLoopback) {
log.warn(
{ pluginId: plugin.id, devUiUrl, host: devHost },
"plugin-ui-static: devUiUrl must target localhost, rejecting proxy",
);
res.status(400).json({ error: "devUiUrl must target localhost" });View on GitHub (pinned to 120ae5428f)
Solutions
- Set devUiUrl to a fully qualified http(s) URL, e.g. 'http://localhost:5173/' (scheme is mandatory)
- Double-check for typos such as 'http:/localhost:5173' or trailing text after the port
- After updating the plugin config, re-request the asset — the config is read per request via registry.getConfig
Example fix
# plugin company config — before
{ "devUiUrl": "localhost:5173" }
# after
{ "devUiUrl": "http://localhost:5173/" } Defensive patterns
Strategy: validation
Validate before calling
const isValidDevUiUrl = (u: string): boolean => {
try {
const parsed = new URL(u);
return parsed.protocol === "http:" || parsed.protocol === "https:";
} catch {
return false;
}
};
if (!isValidDevUiUrl(config.devUiUrl)) throw new Error("devUiUrl must be a full http(s) URL"); Type guard
const isHttpUrl = (u: string): u is `http${"s" | ""}://${string}` => {
try { return ["http:", "https:"].includes(new URL(u).protocol); } catch { return false; }
}; Prevention
- Always include the http:// scheme in devUiUrl ('localhost:5173' parses as protocol 'localhost:')
- Validate devUiUrl with new URL() before saving company plugin config
When it happens
Trigger: Company plugin config configJson.devUiUrl set to a non-http(s) value: 'file:///home/me/plugin/dist/ui/', 'ftp://...', or most commonly a scheme-less value like 'localhost:5173' or '127.0.0.1:5173' — new URL('localhost:5173/') parses 'localhost:' as the protocol, so the constructed URL is neither http nor https and the request 400s.
Common situations: Plugin authors configuring hot-reload per PLUGIN_SPEC §27.2 and forgetting the http:// scheme; copy-pasting a filesystem path instead of a dev-server URL; template config files with placeholder values; environment changes where the dev server URL was edited by hand.
Related errors
- devUiUrl must target localhost
- Invalid file path
- Invalid status '${String(rawStatus)}'. Must be one of: ${PLU
- Plugin tool dispatch is not enabled
- Request body is required
AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18).
Data as JSON: /api/errors/9369c501e562c4dd.
Report an issue: GitHub.