google-gemini/gemini-cli · error · OAuthSecurityError

Failed to resolve hostname

Error message

Failed to resolve hostname "${hostname}" for OAuth endpoint "${resolvedUrl}".

What it means

For non-loopback hosts, the library performs DNS resolution (lookup with { all: true }) as an SSRF/DNS-rebinding defense, and the lookup returned no addresses (empty array). The hostname is syntactically fine but resolves to nothing at validation time.

Solutions

  1. Verify the hostname resolves from this environment: 'nslookup <hostname>' or 'node -e "require('dns').lookup(process.argv[1],{all:true},console.log)"'
  2. Fix typos in the configured OAuth endpoint hostname
  3. If using an internal DNS name, ensure the process's resolver can reach that DNS server (VPN, /etc/resolv.conf, Node's dns.setDefaultResultOrder caveats)
  4. Retry after DNS propagation or resolver recovery — this check is environment- and time-dependent

Example fix

// before
await validateOAuthEndpointUrl('https://auth.exmaple.com/authorize'); // typo'd domain, NXDOMAIN

// after
await validateOAuthEndpointUrl('https://auth.example.com/authorize');
Defensive patterns

Strategy: retry

Validate before calling

import { lookup } from 'node:dns/promises';

async function resolvesToAddresses(host: string): Promise<boolean> {
  try { const a = await lookup(host, { all: true }); return a.length > 0; } catch { return false; }
}

if (!(await resolvesToAddresses(new URL(endpoint).hostname))) {
  throw new Error(`Hostname does not resolve: ${endpoint}`);
}

Type guard

async function isResolvableHost(host: string): Promise<boolean> {
  try { return (await lookup(host, { all: true })).length > 0; } catch { return false; }
}

Try / catch

try {
  await validateOAuthEndpointUrl(endpoint);
} catch (e) {
  if (e instanceof OAuthSecurityError && e.message.includes('Failed to resolve hostname')) {
    // DNS returned no records: check typos/propagation/resolver, then retry after a delay
  }
  throw e;
}

Prevention

When it happens

Trigger: validateOAuthEndpointUrl called with a hostname that DNS cannot resolve to any A/AAAA record — e.g. a typo'd domain, a not-yet-propagated record, an internal DNS name unknown to the resolver, or a temporarily failing resolver returning an empty result.

Common situations: Newly deployed OAuth hosts whose DNS records haven't propagated; corporate/internal DNS names resolved from outside the network; transient DNS flakiness in CI; air-gapped environments where public DNS is unavailable.

Related errors


AI-assisted analysis of google-gemini/gemini-cli@3c311beac2 (2026-08-27). Data as JSON: /api/errors/b9a444fb03af55f6. Report an issue: GitHub.

Appendix: source

Thrown at packages/core/src/mcp/oauth-utils.ts:139

      throw new OAuthSecurityError(
        `Loopback OAuth endpoint "${resolvedUrl}" is not allowed for remote MCP servers.`,
      );
    }
    return parsed.toString();
  }

  // Non-loopback host: check literal IP
  if (isAddressPrivate(hostname)) {
    throw new OAuthSecurityError(
      `OAuth endpoint "${resolvedUrl}" points to private or reserved IP address which is blocked.`,
    );
  }

  // Asynchronous DNS resolution to prevent DNS rebinding / SSRF
  try {
    const addresses = await lookup(hostname, { all: true });
    if (!addresses || addresses.length === 0) {
      throw new OAuthSecurityError(
        `Failed to resolve hostname "${hostname}" for OAuth endpoint "${resolvedUrl}".`,
      );
    }

    for (const addr of addresses) {
      if (isAddressPrivate(addr.address)) {
        throw new OAuthSecurityError(
          `OAuth endpoint "${resolvedUrl}" resolves to private network address "${addr.address}" which is blocked.`,
        );
      }
    }
  } catch (error) {
    if (error instanceof OAuthSecurityError) {
      throw error;
    }
    throw new OAuthSecurityError(
      `DNS lookup failed for OAuth endpoint host "${hostname}": ${getErrorMessage(error)}`,
    );

View on GitHub (pinned to 3c311beac2)