{"record":{"id":"bc085a3c554d8ce0","repo":"google-gemini/gemini-cli","slug":"insecure-http-oauth-endpoint-resolvedurl-is-n","errorCode":null,"errorMessage":"Insecure HTTP OAuth endpoint \"${resolvedUrl}\" is not allowed. OAuth endpoints must use HTTPS unless connecting to localhost.","messagePattern":"Insecure HTTP OAuth endpoint \"(.+?)\" is not allowed\\. OAuth endpoints must use HTTPS unless connecting to localhost\\.","errorType":"exception","errorClass":"OAuthSecurityError","httpStatus":null,"severity":"error","filePath":"packages/core/src/mcp/oauth-utils.ts","lineNumber":98,"sourceCode":"  } catch (e) {\n    throw new OAuthSecurityError(\n      `Invalid OAuth endpoint URL \"${resolvedUrl}\": ${getErrorMessage(e)}`,\n    );\n  }\n\n  const isHttp = parsed.protocol === 'http:';\n  const isHttps = parsed.protocol === 'https:';\n  if (!isHttp && !isHttps) {\n    throw new OAuthSecurityError(\n      `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    }","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/3c311beac2e78336816dd4a123db39743f9fbf85/packages/core/src/mcp/oauth-utils.ts#L80-L116","documentation":"The endpoint uses plain http:// but the host is not a loopback address (or allowLoopback was not requested). OAuth traffic over unencrypted HTTP leaks credentials and tokens, so the library restricts HTTP to loopback development scenarios (localhost, 127.0.0.1, ::1).","triggerScenarios":"Passing an http:// URL with a non-loopback host, e.g. 'http://auth.example.com/token'; or an http:// loopback URL without { allowLoopback: true } in options.","commonSituations":"Local development against a server on the LAN or a docker host accessed by IP (e.g. http://192.168.x.x) instead of localhost; staging environments without TLS certificates; env-var configurations defaulting to http in production.","solutions":["Use an https:// endpoint for anything non-loopback (the primary fix)","For local development, use a loopback hostname (localhost, 127.0.0.1, [::1]) and pass { allowLoopback: true }","If the service is only reachable over HTTP on the LAN (e.g. Docker), tunnel it to localhost (e.g. ssh -L) or add TLS via a local reverse proxy","Check environment-specific config to ensure production overrides don't leave http:// defaults"],"exampleFix":"// before\nawait validateOAuthEndpointUrl('http://auth.example.com/authorize');\n\n// after\nawait validateOAuthEndpointUrl('https://auth.example.com/authorize');\n// or, for local dev:\nawait validateOAuthEndpointUrl('http://localhost:3000/authorize', { allowLoopback: true });","handlingStrategy":"validation","validationCode":"function isSecureOrLoopbackHttp(v: string): boolean {\n  try {\n    const u = new URL(v);\n    if (u.protocol === 'https:') return true;\n    if (u.protocol !== 'http:') return false;\n    return ['localhost', '127.0.0.1', '[::1]'].includes(u.hostname) || /^127\\./.test(u.hostname);\n  } catch { return false; }\n}\n\nif (process.env.NODE_ENV === 'production' && !endpoint.startsWith('https://')) {\n  throw new Error('OAuth endpoints must use HTTPS in production');\n}","typeGuard":"function isHttpsUrl(v: string): v is `https://${string}` {\n  return v.startsWith('https://');\n}","tryCatchPattern":"try {\n  await validateOAuthEndpointUrl(endpoint, { allowLoopback: isLocalDev });\n} catch (e) {\n  if (e instanceof OAuthSecurityError && e.message.includes('Insecure HTTP OAuth endpoint')) {\n    // switch to https, or use a loopback host with allowLoopback: true for local dev\n  }\n  throw e;\n}","preventionTips":["Default all OAuth endpoint config to https://; require an explicit opt-in for http","Reserve http:// for localhost development only","Provision TLS even in staging (e.g. mkcert or a reverse proxy) so dev mirrors production"],"tags":["oauth","tls","security","ssrf-protection"],"backgroundTag":"insecure-http-endpoint","analyzedSha":"3c311beac2e78336816dd4a123db39743f9fbf85","analyzedAt":"2026-08-27T19:07:12.298Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}