google-gemini/gemini-cli · error · OAuthSecurityError

Invalid expected origin "${options.expectedOrigin}".

Error message

Invalid expected origin "${options.expectedOrigin}".

What it means

The caller supplied options.expectedOrigin, but that value itself cannot be parsed as a URL, so its origin cannot be computed for comparison. This is a caller-configuration bug: the pinning origin is malformed before the endpoint is even compared against it.

Source

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

      `Invalid OAuth endpoint protocol "${parsed.protocol}". Only HTTPS (and HTTP for local development) is supported.`,
    );
  }

  const hostname = sanitizeHostname(parsed.hostname);
  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();
  }

View on GitHub (pinned to 3c311beac2)

Solutions

  1. Set expectedOrigin to a clean origin string with scheme and host only, e.g. 'https://auth.example.com' (the code normalizes via URL.origin, but it must at least parse)
  2. Remove any path, query, or fragment from the configured origin
  3. Validate the config value at startup with new URL(origin) and fail fast with a clear config error

Example fix

// before
await validateOAuthEndpointUrl(url, { expectedOrigin: process.env.OAUTH_ORIGIN! }); // 'auth.example.com'

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

Strategy: validation

Validate before calling

function isParseableOrigin(v: string): boolean {
  try { new URL(v); return true; } catch { return false; }
}

if (expectedOrigin && !isParseableOrigin(expectedOrigin)) {
  throw new Error(`Config bug: expectedOrigin '${expectedOrigin}' is not a valid URL`);
}

Type guard

function isValidOrigin(v: unknown): v is string {
  return typeof v === 'string' && (() => { try { new URL(v); return true; } catch { return false; } })();
}

Try / catch

try {
  await validateOAuthEndpointUrl(url, { expectedOrigin });
} catch (e) {
  if (e instanceof OAuthSecurityError && e.message.includes('Invalid expected origin')) {
    // the expectedOrigin config itself is malformed; fix it (scheme + host, no path)
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling validateOAuthEndpointUrl(url, { expectedOrigin: 'auth.example.com' }) (missing scheme), or with a path/query string ('https://host/path'), garbage string, or empty-ish value that fails new URL().

Common situations: Expected-origin values read from env vars or config files that lack the scheme, contain typos, or include path components; values copied from browser address bars with trailing slashes or paths.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


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