different-ai/openwork · error · LocalManagedMcpPrivateUrlError
embedded URL credentials are not allowed
Error message
embedded URL credentials are not allowed
What it means
LocalManagedMcpPrivateUrlError thrown by parseHttpUrl when the configured MCP URL embeds userinfo credentials (username or password, e.g. https://user:pass@host). Embedding credentials in URLs is rejected to prevent credential leakage into logs, error messages, and downstream fetches.
Source
Thrown at apps/server/src/local-managed-mcp-url-guard.ts:112
export function isLocalManagedMcpPrivateAddress(address: string): boolean {
const version = isIP(address);
if (version === 4) return isPrivateIpv4(address);
if (version === 6) return isPrivateIpv6(address);
return true;
}
function parseHttpUrl(rawUrl: string): URL {
let url: URL;
try {
url = new URL(rawUrl);
} catch {
throw new LocalManagedMcpPrivateUrlError(rawUrl, "not a valid URL");
}
if (url.protocol !== "http:" && url.protocol !== "https:") {
throw new LocalManagedMcpPrivateUrlError(rawUrl, `protocol "${url.protocol}" is not allowed`);
}
if (url.username || url.password) {
throw new LocalManagedMcpPrivateUrlError(rawUrl, "embedded URL credentials are not allowed");
}
return url;
}
function allowPrivateUrls(): boolean {
return process.env.OPENWORK_DEV_MODE === "1" || process.env.OPENWORK_ALLOW_PRIVATE_MCP_URLS === "1";
}
type ResolveAddresses = (hostname: string, options: LookupAllOptions) => Promise<LookupAddress[]>;
const resolveAddresses: ResolveAddresses = (hostname, options) => lookup(hostname, options);
function validateResolvedAddresses(hostname: string, addresses: LookupAddress[]): void {
if (addresses.length === 0) {
throw new LocalManagedMcpPrivateUrlError(`https://${hostname}/`, "the hostname does not resolve");
}
if (allowPrivateUrls()) return;
for (const { address } of addresses) {View on GitHub (pinned to 2b7df46e8a)
Solutions
- Remove user:pass@ from the URL and pass credentials via HTTP headers (e.g. Authorization) in the server config
- If the MCP server requires basic auth, use its header/config option instead of URL userinfo
- Rotate the credential if it was embedded in a URL — URLs get logged and shared
- Store the secret in an env var and reference it in the config rather than inline
Example fix
// before
{ "url": "https://user:hunter2@mcp.example.com/sse" }
// after
{ "url": "https://mcp.example.com/sse", "headers": { "Authorization": "Basic ..." } } Defensive patterns
Strategy: validation
Validate before calling
function assertNoUrlCredentials(raw: string): void {
const u = new URL(raw);
if (u.username || u.password) throw new Error("move credentials out of the URL into headers");
} Type guard
function hasEmbeddedCredentials(raw: string): boolean {
try { const u = new URL(raw); return u.username !== "" || u.password !== ""; } catch { return false; }
} Try / catch
try {
await mcp.addServer({ url: rawUrl });
} catch (e) {
if (e instanceof LocalManagedMcpPrivateUrlError && e.message.includes("credentials")) {
// strip userinfo, reconfigure auth via headers, rotate the leaked secret
} else throw e;
} Prevention
- Never put user:pass@ or API keys in URLs — use headers
- Lint/scan configs for the @userinfo pattern before deploy
- Rotate any credential that was ever embedded in a URL
When it happens
Trigger: A local managed MCP server URL of the form scheme://user:pass@host/... is parsed: url.username or url.password is non-empty after new URL(rawUrl).
Common situations: Copy-pasting a URL with basic-auth credentials from another tool; putting API keys in the URL instead of headers; legacy configs that predate header-based auth.
Related errors
- An enterprise MCP server URL cannot contain embedded credent
- OpenWork-managed OAuth requires a remote MCP URL.
- protocol "${url.protocol}" is not allowed
- the hostname resolves to a private or reserved address (${ad
- DEN_API_PUBLIC_URL must use HTTPS outside development and lo
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/eee9fb16be1e77fe.
Report an issue: GitHub.