{"record":{"id":"3f8e3c6a08e4378f","repo":"google-gemini/gemini-cli","slug":"failed-to-resolve-relative-oauth-url-urlstr-a","errorCode":null,"errorMessage":"Failed to resolve relative OAuth URL \"${urlStr}\" against base \"${options.baseUri}\": ${getErrorMessage(e)}","messagePattern":"Failed to resolve relative OAuth URL \"(.+?)\" against base \"(.+?)\": (.+?)","errorType":"exception","errorClass":"OAuthSecurityError","httpStatus":null,"severity":"error","filePath":"packages/core/src/mcp/oauth-utils.ts","lineNumber":71,"sourceCode":"  allowLoopback?: boolean;\n  expectedOrigin?: string;\n  allowRelative?: boolean;\n  baseUri?: string;\n}\n\n/**\n * Validates an OAuth endpoint URL against SSRF, scheme, and origin constraints per RFC 9728 Section 7.7.\n */\nexport async function validateOAuthEndpointUrl(\n  urlStr: string,\n  options?: OAuthUrlValidationOptions,\n): Promise<string> {\n  let resolvedUrl = urlStr.trim();\n  if (options?.allowRelative && options.baseUri) {\n    try {\n      resolvedUrl = new URL(resolvedUrl, options.baseUri).toString();\n    } catch (e) {\n      throw new OAuthSecurityError(\n        `Failed to resolve relative OAuth URL \"${urlStr}\" against base \"${options.baseUri}\": ${getErrorMessage(e)}`,\n      );\n    }\n  }\n\n  let parsed: URL;\n  try {\n    parsed = new URL(resolvedUrl);\n  } 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(","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/3c311beac2e78336816dd4a123db39743f9fbf85/packages/core/src/mcp/oauth-utils.ts#L53-L89","documentation":"This error is thrown by validateOAuthEndpointUrl when a relative OAuth URL cannot be resolved against the provided baseUri using the URL constructor. It only occurs when options.allowRelative is true and options.baseUri is set, meaning the library was asked to accept relative endpoints but the combination of the relative string and the base produces an invalid URL. Typical causes are a malformed base URI (e.g. missing scheme like 'example.com/path') or a relative string that cannot be parsed even against the base.","triggerScenarios":"Calling validateOAuthEndpointUrl('/authorize', { allowRelative: true, baseUri: 'not-a-valid-url' }) or passing a relative URL with unsupported syntax (e.g. '//host/path' against a base without a scheme), causing new URL(resolvedUrl, options.baseUri) to throw.","commonSituations":"Reading authorization_server or registration_endpoint fields from MCP resource metadata that contain relative paths (per RFC 8414) while the baseUri was stored without a protocol, contains stray whitespace/quotes, or was built from an env var that is empty or malformed.","solutions":["Verify options.baseUri is an absolute URL including scheme, e.g. 'https://server.example.com' (run new URL(baseUri) yourself to confirm it parses)","Trim whitespace/quotes off both urlStr and baseUri before passing them","If the endpoint is actually absolute, pass it as-is without allowRelative so resolution is skipped","Log both urlStr and baseUri at the call site to identify which of the two is malformed"],"exampleFix":"// before\nawait validateOAuthEndpointUrl(meta.authorization_endpoint, { allowRelative: true, baseUri: serverUrl }); // serverUrl = 'localhost:3000'\n\n// after\nconst base = serverUrl.startsWith('http') ? serverUrl : `https://${serverUrl}`;\nawait validateOAuthEndpointUrl(meta.authorization_endpoint, { allowRelative: true, baseUri: base });","handlingStrategy":"validation","validationCode":"import { URL } from 'node:url';\n\nfunction canResolveRelative(urlStr: string, baseUri: string): boolean {\n  try {\n    new URL(baseUri);                 // base must be absolute\n    new URL(urlStr.trim(), baseUri);  // combination must parse\n    return true;\n  } catch {\n    return false;\n  }\n}","typeGuard":"function isAbsoluteHttpUrl(v: string): boolean {\n  try { const u = new URL(v); return u.protocol === 'http:' || u.protocol === 'https:'; }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  await validateOAuthEndpointUrl(rel, { allowRelative: true, baseUri });\n} catch (e) {\n  if (e instanceof OAuthSecurityError && e.message.startsWith('Failed to resolve relative OAuth URL')) {\n    // fix baseUri (ensure scheme) or pass an absolute endpoint, then retry\n  }\n  throw e;\n}","preventionTips":["Normalize baseUri through new URL(base).toString() before passing it","Validate OAuth config (endpoints, base URI) at startup rather than mid-flow","Keep base URIs in one config module so scheme mistakes can't be introduced per call-site"],"tags":["oauth","url-parsing","ssrf-protection","mcp"],"backgroundTag":"invalid-url","analyzedSha":"3c311beac2e78336816dd4a123db39743f9fbf85","analyzedAt":"2026-08-27T19:07:12.298Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}