{"record":{"id":"b9a444fb03af55f6","repo":"google-gemini/gemini-cli","slug":"failed-to-resolve-hostname-hostname-for-oauth","errorCode":null,"errorMessage":"Failed to resolve hostname \"${hostname}\" for OAuth endpoint \"${resolvedUrl}\".","messagePattern":"Failed to resolve hostname \"(.+?)\" for OAuth endpoint \"(.+?)\"\\.","errorType":"exception","errorClass":"OAuthSecurityError","httpStatus":null,"severity":"error","filePath":"packages/core/src/mcp/oauth-utils.ts","lineNumber":139,"sourceCode":"      throw new OAuthSecurityError(\n        `Loopback OAuth endpoint \"${resolvedUrl}\" is not allowed for remote MCP servers.`,\n      );\n    }\n    return parsed.toString();\n  }\n\n  // Non-loopback host: check literal IP\n  if (isAddressPrivate(hostname)) {\n    throw new OAuthSecurityError(\n      `OAuth endpoint \"${resolvedUrl}\" points to private or reserved IP address which is blocked.`,\n    );\n  }\n\n  // Asynchronous DNS resolution to prevent DNS rebinding / SSRF\n  try {\n    const addresses = await lookup(hostname, { all: true });\n    if (!addresses || addresses.length === 0) {\n      throw new OAuthSecurityError(\n        `Failed to resolve hostname \"${hostname}\" for OAuth endpoint \"${resolvedUrl}\".`,\n      );\n    }\n\n    for (const addr of addresses) {\n      if (isAddressPrivate(addr.address)) {\n        throw new OAuthSecurityError(\n          `OAuth endpoint \"${resolvedUrl}\" resolves to private network address \"${addr.address}\" which is blocked.`,\n        );\n      }\n    }\n  } catch (error) {\n    if (error instanceof OAuthSecurityError) {\n      throw error;\n    }\n    throw new OAuthSecurityError(\n      `DNS lookup failed for OAuth endpoint host \"${hostname}\": ${getErrorMessage(error)}`,\n    );","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/3c311beac2e78336816dd4a123db39743f9fbf85/packages/core/src/mcp/oauth-utils.ts#L121-L157","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the hostname resolves from this environment: 'nslookup <hostname>' or 'node -e \"require('dns').lookup(process.argv[1],{all:true},console.log)\"'","Fix typos in the configured OAuth endpoint hostname","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)","Retry after DNS propagation or resolver recovery — this check is environment- and time-dependent"],"exampleFix":"// before\nawait validateOAuthEndpointUrl('https://auth.exmaple.com/authorize'); // typo'd domain, NXDOMAIN\n\n// after\nawait validateOAuthEndpointUrl('https://auth.example.com/authorize');","handlingStrategy":"retry","validationCode":"import { lookup } from 'node:dns/promises';\n\nasync function resolvesToAddresses(host: string): Promise<boolean> {\n  try { const a = await lookup(host, { all: true }); return a.length > 0; } catch { return false; }\n}\n\nif (!(await resolvesToAddresses(new URL(endpoint).hostname))) {\n  throw new Error(`Hostname does not resolve: ${endpoint}`);\n}","typeGuard":"async function isResolvableHost(host: string): Promise<boolean> {\n  try { return (await lookup(host, { all: true })).length > 0; } catch { return false; }\n}","tryCatchPattern":"try {\n  await validateOAuthEndpointUrl(endpoint);\n} catch (e) {\n  if (e instanceof OAuthSecurityError && e.message.includes('Failed to resolve hostname')) {\n    // DNS returned no records: check typos/propagation/resolver, then retry after a delay\n  }\n  throw e;\n}","preventionTips":["Smoke-test DNS resolution for OAuth hosts in deployment health checks","Pin validated endpoint URLs in a cache so flaky DNS doesn't repeatedly break flows","In CI, avoid real external hostnames — use local loopback endpoints with allowLoopback"],"tags":["oauth","dns","ssrf-protection","network"],"backgroundTag":"dns-resolution-failed","analyzedSha":"3c311beac2e78336816dd4a123db39743f9fbf85","analyzedAt":"2026-08-27T19:07:12.298Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}