{"record":{"id":"5d1cac1e41a28731","repo":"google-gemini/gemini-cli","slug":"invalid-expected-origin-options-expectedorigin","errorCode":null,"errorMessage":"Invalid expected origin \"${options.expectedOrigin}\".","messagePattern":"Invalid expected origin \"(.+?)\"\\.","errorType":"exception","errorClass":"OAuthSecurityError","httpStatus":null,"severity":"error","filePath":"packages/core/src/mcp/oauth-utils.ts","lineNumber":108,"sourceCode":"      `Invalid OAuth endpoint protocol \"${parsed.protocol}\". Only HTTPS (and HTTP for local development) is supported.`,\n    );\n  }\n\n  const hostname = sanitizeHostname(parsed.hostname);\n  const isLoopback = isLoopbackHost(hostname);\n\n  if (isHttp && (!options?.allowLoopback || !isLoopback)) {\n    throw new OAuthSecurityError(\n      `Insecure HTTP OAuth endpoint \"${resolvedUrl}\" is not allowed. OAuth endpoints must use HTTPS unless connecting to localhost.`,\n    );\n  }\n\n  if (options?.expectedOrigin) {\n    let expected: string;\n    try {\n      expected = new URL(options.expectedOrigin).origin;\n    } catch {\n      throw new OAuthSecurityError(\n        `Invalid expected origin \"${options.expectedOrigin}\".`,\n      );\n    }\n    if (parsed.origin !== expected) {\n      throw new OAuthSecurityError(\n        `OAuth endpoint origin \"${parsed.origin}\" does not match expected origin \"${expected}\".`,\n      );\n    }\n  }\n\n  if (isLoopback) {\n    if (!options?.allowLoopback) {\n      throw new OAuthSecurityError(\n        `Loopback OAuth endpoint \"${resolvedUrl}\" is not allowed for remote MCP servers.`,\n      );\n    }\n    return parsed.toString();\n  }","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/3c311beac2e78336816dd4a123db39743f9fbf85/packages/core/src/mcp/oauth-utils.ts#L90-L126","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","solutions":["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)","Remove any path, query, or fragment from the configured origin","Validate the config value at startup with new URL(origin) and fail fast with a clear config error"],"exampleFix":"// before\nawait validateOAuthEndpointUrl(url, { expectedOrigin: process.env.OAUTH_ORIGIN! }); // 'auth.example.com'\n\n// after\nawait validateOAuthEndpointUrl(url, { expectedOrigin: 'https://auth.example.com' });","handlingStrategy":"validation","validationCode":"function isParseableOrigin(v: string): boolean {\n  try { new URL(v); return true; } catch { return false; }\n}\n\nif (expectedOrigin && !isParseableOrigin(expectedOrigin)) {\n  throw new Error(`Config bug: expectedOrigin '${expectedOrigin}' is not a valid URL`);\n}","typeGuard":"function isValidOrigin(v: unknown): v is string {\n  return typeof v === 'string' && (() => { try { new URL(v); return true; } catch { return false; } })();\n}","tryCatchPattern":"try {\n  await validateOAuthEndpointUrl(url, { expectedOrigin });\n} catch (e) {\n  if (e instanceof OAuthSecurityError && e.message.includes('Invalid expected origin')) {\n    // the expectedOrigin config itself is malformed; fix it (scheme + host, no path)\n  }\n  throw e;\n}","preventionTips":["Store expectedOrigin as scheme+host only (https://auth.example.com)","Validate it with new URL() at startup and fail fast","Keep expectedOrigin in typed config, not ad-hoc strings at call sites"],"tags":["oauth","origin-pinning","url-parsing","configuration"],"backgroundTag":"invalid-url","analyzedSha":"3c311beac2e78336816dd4a123db39743f9fbf85","analyzedAt":"2026-08-27T19:07:12.298Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}