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
- 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
- Fix scheme/port mismatches: URL.origin includes the port when non-default, so 'https://host:8443' ≠ 'https://host'
- 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
- 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
- Derive expectedOrigin from the same trusted source as the server URL rather than hardcoding a second value
- Remember URL.origin includes non-default ports — include them in the pinned origin
- Alert on unexpected origin mismatches; they can indicate compromised metadata
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
- Insecure HTTP OAuth endpoint "${resolvedUrl}" is not allowed
- Failed to resolve relative OAuth URL "${urlStr}" against bas
- Invalid OAuth endpoint protocol "${parsed.protocol}". Only H
- Invalid expected origin "${options.expectedOrigin}".
- Loopback OAuth endpoint "${resolvedUrl}" is not allowed for
AI-assisted analysis of google-gemini/gemini-cli@3c311beac2 (2026-08-27).
Data as JSON: /api/errors/5c85a2a70688de1a.
Report an issue: GitHub.