google-gemini/gemini-cli · error · OAuthSecurityError
Loopback OAuth endpoint "${resolvedUrl}" is not allowed for
Error message
Loopback OAuth endpoint "${resolvedUrl}" is not allowed for remote MCP servers. What it means
The endpoint host is a loopback address (localhost / 127.x / ::1) but the caller did not pass { allowLoopback: true }. Remote MCP OAuth endpoints are never expected to live on the client's own machine, so loopback endpoints are blocked by default — this blocks SSRF-style attacks that redirect OAuth flows to local services.
Source
Thrown at packages/core/src/mcp/oauth-utils.ts:121
if (options?.expectedOrigin) {
let expected: string;
try {
expected = new URL(options.expectedOrigin).origin;
} catch {
throw new OAuthSecurityError(
`Invalid expected origin "${options.expectedOrigin}".`,
);
}
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(View on GitHub (pinned to 3c311beac2)
Solutions
- Pass { allowLoopback: true } when validating local development endpoints
- For anything other than local dev, point the endpoint at the real remote host instead of localhost
- In tests, ensure the test harness/config enables allowLoopback for local mock servers
Example fix
// before
await validateOAuthEndpointUrl('http://localhost:3000/authorize');
// after
await validateOAuthEndpointUrl('http://localhost:3000/authorize', { allowLoopback: true }); Defensive patterns
Strategy: validation
Validate before calling
const isLocalDev = process.env.NODE_ENV !== 'production';
const isLoopback = (() => { try { const h = new URL(endpoint).hostname; return h === 'localhost' || h.startsWith('127.') || h === '[::1]' || h === '::1'; } catch { return false; } })();
const opts = { allowLoopback: isLocalDev && isLoopback };
await validateOAuthEndpointUrl(endpoint, opts); Type guard
function isLoopbackEndpoint(v: string): boolean {
try { const h = new URL(v).hostname; return h === 'localhost' || h.startsWith('127.') || h === '::1'; } catch { return false; }
} Try / catch
try {
await validateOAuthEndpointUrl(endpoint);
} catch (e) {
if (e instanceof OAuthSecurityError && e.message.includes('not allowed for remote MCP servers')) {
// localhost endpoint: retry with { allowLoopback: true } in dev, or use the real remote host in prod
}
throw e;
} Prevention
- Gate allowLoopback behind an explicit isLocalDev flag so production can never enable it
- Use localhost URLs only for local mock auth servers in tests
- For production, always validate against the server's real public hostname
When it happens
Trigger: Passing 'http://localhost:3000/authorize' or 'https://127.0.0.1:8443/token' to validateOAuthEndpointUrl without allowLoopback: true (note: loopback check happens before the HTTP check, so even https loopback hits this).
Common situations: Local development against a locally hosted auth server where the caller forgot the allowLoopback option; integration tests pointing at a local mock OAuth server; discovered metadata from a local server being validated with remote-server settings.
Related errors
- 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
- OAuth endpoint "${resolvedUrl}" points to private or reserve
AI-assisted analysis of google-gemini/gemini-cli@3c311beac2 (2026-08-27).
Data as JSON: /api/errors/157585c6018a7eb6.
Report an issue: GitHub.