google-gemini/gemini-cli · error · OAuthSecurityError

OAuth endpoint origin "${parsed.origin}" does not match expe

Error message

OAuth endpoint origin "${parsed.origin}" does not match expected origin "${expected}".

What it means

The endpoint URL parses fine, but its origin (scheme + host + port) does not match the expectedOrigin the caller pinned. This is an anti-SSRF/redirect-attack check: OAuth metadata discovered from a server must not point the client at a different origin than the one the operator explicitly trusted.

Source

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

  const isLoopback = isLoopbackHost(hostname);

  if (isHttp && (!options?.allowLoopback || !isLoopback)) {
    throw new OAuthSecurityError(
      `Insecure HTTP OAuth endpoint "${resolvedUrl}" is not allowed. OAuth endpoints must use HTTPS unless connecting to localhost.`,
    );
  }

  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.`,

View on GitHub (pinned to 3c311beac2)

Solutions

  1. Confirm the endpoint actually should be on the pinned origin; if the metadata legitimately points elsewhere (dedicated identity provider), update expectedOrigin to that trusted origin
  2. Fix scheme/port mismatches: URL.origin includes the port when non-default, so 'https://host:8443' ≠ 'https://host'
  3. If the mismatch is unexpected, treat it as a security signal — verify the server's OAuth metadata has not been tampered with before overriding anything
  4. Ensure the value used for expectedOrigin is derived from the same source (e.g. the MCP server URL) you intend to trust

Example fix

// before
await validateOAuthEndpointUrl(metadataEndpoint, { expectedOrigin: 'https://api.example.com' });
// metadataEndpoint = 'https://auth.example.com/authorize' -> mismatch

// after
await validateOAuthEndpointUrl(metadataEndpoint, { expectedOrigin: 'https://auth.example.com' });
Defensive patterns

Strategy: try-catch

Validate before calling

function originsMatch(endpoint: string, expectedOrigin: string): boolean | null {
  try {
    return new URL(endpoint).origin === new URL(expectedOrigin).origin;
  } catch { return null; }
}

const ok = originsMatch(endpoint, expectedOrigin);
if (ok === false) console.warn('Endpoint origin differs from pinned origin — verify before proceeding');

Type guard

function matchesOrigin(endpoint: string, origin: string): endpoint is string {
  try { return new URL(endpoint).origin === new URL(origin).origin; } catch { return false; }
}

Try / catch

try {
  await validateOAuthEndpointUrl(url, { expectedOrigin });
} catch (e) {
  if (e instanceof OAuthSecurityError && e.message.includes('does not match expected origin')) {
    // decide deliberately: update the pinned origin (if the new host is trusted) or abort (possible SSRF/redirect attack)
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling validateOAuthEndpointUrl('https://evil.example.com/authorize', { expectedOrigin: 'https://auth.example.com' }); also legitimate mismatches like a port difference (https://host:8443 vs https://host) or scheme difference (http vs https).

Common situations: Compromised or misconfigured server advertising OAuth endpoints on a different domain than its own; CDN/proxy setups where metadata references an internal host; ports omitted or added between environments; http-vs-https drift between config and discovered metadata.

Related errors


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