different-ai/openwork · error · LocalManagedMcpPrivateUrlError

the hostname resolves to a private or reserved address (${ad

Error message

the hostname resolves to a private or reserved address (${address})

What it means

LocalManagedMcpPrivateUrlError thrown by validateResolvedAddresses when any resolved IP for the MCP hostname is a private/reserved address (loopback, RFC1918, link-local, etc.) while private URLs are not allowed. This is an SSRF protection: remote-looking hostnames that actually resolve to internal networks are rejected. Dev can bypass via OPENWORK_DEV_MODE=1 or OPENWORK_ALLOW_PRIVATE_MCP_URLS=1.

Source

Thrown at apps/server/src/local-managed-mcp-url-guard.ts:132

  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) {
    if (isLocalManagedMcpPrivateAddress(address)) {
      throw new LocalManagedMcpPrivateUrlError(
        `https://${hostname}/`,
        `the hostname resolves to a private or reserved address (${address})`,
      );
    }
  }
}

/**
 * Resolves and validates the address inside the socket connector's lookup
 * callback. The exact validated answer is handed to net.connect, so a later
 * DNS answer cannot replace it between validation and connection.
 */
export function createLocalManagedMcpPublicLookup(
  resolver: ResolveAddresses = resolveAddresses,
): LookupFunction {
  return (hostname, options, callback) => {
    const lookupOptions: LookupAllOptions = { ...options, all: true, verbatim: true };
    void resolver(hostname, lookupOptions).then((addresses) => {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. If the private URL is intentional (local dev), set OPENWORK_ALLOW_PRIVATE_MCP_URLS=1 (or OPENWORK_DEV_MODE=1) for the server process
  2. Otherwise point the config at the server's real public hostname/IP
  3. Check /etc/hosts and DNS for entries mapping the hostname to a private address
  4. Never disable the guard in production-facing deployments — it exists to block SSRF

Example fix

// before
{ "url": "https://my-mcp.example.com/sse" }  // resolves to 127.0.0.1
// after (local dev, intentional)
OPENWORK_ALLOW_PRIVATE_MCP_URLS=1 openwork-server ...
// or point at the public address
{ "url": "https://mcp-prod.example.com/sse" }
Defensive patterns

Strategy: validation

Validate before calling

import { lookup } from "node:dns/promises";
function isPrivate(ip: string): boolean {
  return ip === "::1" || ip.startsWith("127.") || ip.startsWith("10.") || ip.startsWith("192.168.") || /^172\.(1[6-9]|2\d|3[01])\./.test(ip) || ip.startsWith("169.254.");
}
const addrs = await lookup(new URL(url).hostname, { all: true });
if (addrs.some(a => isPrivate(a.address)) && process.env.OPENWORK_ALLOW_PRIVATE_MCP_URLS !== "1") throw new Error("hostname resolves to a private address; set OPENWORK_ALLOW_PRIVATE_MCP_URLS=1 if intentional");

Try / catch

try {
  await mcp.addServer({ url });
} catch (e) {
  if (e instanceof LocalManagedMcpPrivateUrlError && e.message.includes("private or reserved")) {
    if (isLocalDev) process.env.OPENWORK_ALLOW_PRIVATE_MCP_URLS = "1"; // then retry
    else throw new Error("refusing internal-resolving host in production");
  } else throw e;
}

Prevention

When it happens

Trigger: lookup(hostname) returns at least one address for which isLocalManagedMcpPrivateAddress(address) is true, and allowPrivateUrls() is false (neither env flag set to "1"). E.g. a public hostname with a DNS rebinding/hosts-file entry pointing at 127.0.0.1 or 10.x.x.x.

Common situations: Intentionally running an MCP server on localhost/LAN while the guard expects public URLs — needs the dev-mode env flag; /etc/hosts entries redirecting a name to 127.0.0.1; DNS rebinding protection firing on a legitimately internal service.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/a2619e80ff63e86c. Report an issue: GitHub.