google-gemini/gemini-cli · error · OAuthSecurityError
OAuth endpoint "${resolvedUrl}" points to private or reserve
Error message
OAuth endpoint "${resolvedUrl}" points to private or reserved IP address which is blocked. What it means
The non-loopback endpoint hostname is a literal IP address in a private or reserved range (e.g. 10.x, 192.168.x, 172.16-31.x, 169.254.x, 0.0.0.0, or equivalent IPv6). Directing OAuth flows at private networks enables SSRF attacks, so literal private IPs are rejected before DNS is even consulted.
Source
Thrown at packages/core/src/mcp/oauth-utils.ts:130
if (parsed.origin !== expected) {
throw new OAuthSecurityError(
`OAuth endpoint origin "${parsed.origin}" does not match expected origin "${expected}".`,
);
}
}
if (isLoopback) {
if (!options?.allowLoopback) {
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.`,
);View on GitHub (pinned to 3c311beac2)
Solutions
- Use a public, resolvable hostname for the OAuth endpoint and expose it via TLS (the intended production shape)
- If this is genuinely an internal deployment, put a DNS name on the service and ensure it resolves to a public address, or run the validation with a hostname whose DNS points where intended — note literal private IPs are unconditionally blocked, so the fix is to not use a raw private IP in the URL
- For containerized setups, route through an ingress/tunnel (e.g. TLS-terminated proxy with a public hostname) rather than the internal IP
- If a hostname is used instead of a literal IP, see error 9 for the DNS-based equivalent
Example fix
// before
await validateOAuthEndpointUrl('https://192.168.1.10/oauth/authorize');
// after
await validateOAuthEndpointUrl('https://auth.internal.example.com/oauth/authorize'); Defensive patterns
Strategy: validation
Validate before calling
import { isIP } from 'node:net';
function isPrivateLiteralIp(v: string): boolean {
try {
const h = new URL(v).hostname.replace(/^\[|\]$/g, '');
if (isIP(h) === 0) return false; // hostname, not literal IP
return (
h.startsWith('10.') || h.startsWith('192.168.') ||
/^172\.(1[6-9]|2\d|3[01])\./.test(h) || h.startsWith('169.254.') ||
h === '0.0.0.0' || h.startsWith('fe80:') || h.startsWith('fc') || h.startsWith('fd')
);
} catch { return false; }
}
if (isPrivateLiteralIp(endpoint)) throw new Error('Refusing private-IP OAuth endpoint'); Type guard
function isPublicHostUrl(v: string): boolean {
try { const h = new URL(v).hostname; return !isPrivateLiteralIp(v) && isIP(h) !== 0 ? true : !isPrivateLiteralIp(v); } catch { return false; }
} Try / catch
try {
await validateOAuthEndpointUrl(endpoint);
} catch (e) {
if (e instanceof OAuthSecurityError && e.message.includes('private or reserved IP address')) {
// raw private IPs are always blocked; switch to a hostname with a public DNS record / public ingress
}
throw e;
} Prevention
- Never configure OAuth endpoints as raw private IPs; use DNS names
- For internal deployments, expose the auth server through a TLS ingress with a public hostname
- Add a config lint that rejects URLs whose hostname is a private-range literal IP
When it happens
Trigger: Passing 'https://192.168.1.10/authorize', 'https://10.0.0.5/token', or 'https://[fe80::1]/authorize' (non-loopback private/reserved literal IPs) to validateOAuthEndpointUrl.
Common situations: Internal/on-prem deployments addressed by raw private IPs; Docker/Kubernetes setups where the discovered endpoint is an internal service IP (e.g. 172.x docker bridge range); home-lab servers referenced by LAN IP; metadata endpoints accidentally returning internal addresses.
Related errors
- OAuth endpoint "${resolvedUrl}" resolves to private network
- Failed to resolve relative OAuth URL "${urlStr}" against bas
- Invalid OAuth endpoint protocol "${parsed.protocol}". Only H
- Insecure HTTP OAuth endpoint "${resolvedUrl}" is not allowed
- OAuth endpoint origin "${parsed.origin}" does not match expe
AI-assisted analysis of google-gemini/gemini-cli@3c311beac2 (2026-08-27).
Data as JSON: /api/errors/202a3caea39605e9.
Report an issue: GitHub.