{"record":{"id":"df00aff19c5b921a","repo":"JuliusBrussee/caveman","slug":"private-device-authorization-returned-an-unsafe-browser-url","errorCode":null,"errorMessage":"private device authorization returned an unsafe browser URL","messagePattern":"private device authorization returned an unsafe browser URL","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":9608,"sourceCode":"}\n\nfunction secureLoginURL(url: URL, allowLoopback = true): boolean {\n  return !url.username && !url.password && (url.protocol === \"https:\" ||\n    (allowLoopback && url.protocol === \"http:\" && [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname)));\n}\n\nfunction privateVerificationURL(code: Record<string, unknown>, instance: string): string {\n  if (typeof code.device_code !== \"string\" || !code.device_code || code.device_code.length > 4096 ||\n      typeof code.user_code !== \"string\" || !/^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/.test(code.user_code) ||\n      typeof code.expires_in !== \"number\" || !Number.isFinite(code.expires_in) || code.expires_in <= 0 || code.expires_in > 3600 ||\n      (code.interval !== undefined && (typeof code.interval !== \"number\" || !Number.isFinite(code.interval) || code.interval < 0 || code.interval > 60))) {\n    throw new Error(\"private device authorization returned an invalid code response\");\n  }\n  const value = code.verification_uri_complete ?? code.verification_uri;\n  if (typeof value !== \"string\") throw new Error(\"private device authorization omitted its browser URL\");\n  const url = new URL(value);\n  if (!secureLoginURL(url, new URL(instance).protocol === \"http:\") || url.hash) {\n    throw new Error(\"private device authorization returned an unsafe browser URL\");\n  }\n  url.searchParams.set(\"user_code\", code.user_code);\n  url.searchParams.set(\"connection\", \"mcp\");\n  url.searchParams.set(\"client_name\", \"Caveman CLI\");\n  return url.href;\n}\n\nfunction openLoginBrowser(url: string): void {\n  const opener = loginBrowserOpener(url);\n  if (!which(opener.command)) {\n    process.stderr.write(`  browser opener unavailable; open ${url}\\n`);\n    return;\n  }\n  const child = spawn(opener.command, opener.args, { detached: true, stdio: \"ignore\", windowsHide: true });\n  child.once(\"error\", () => process.stderr.write(`  browser did not open; open ${url}\\n`));\n  child.unref();\n}\n","sourceCodeStart":9590,"sourceCodeEnd":9626,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L9590-L9626","documentation":"During private-instance device authorization the CLI builds the browser verification URL from the server's verification_uri(_complete). Before opening it, it validates the URL is secure (https, or http only for an http instance) and carries no fragment. This error means the authorization server returned a verification URL that fails those safety checks, so the CLI refuses to open it.","triggerScenarios":"The private instance's OAuth authorization server returns a verification_uri or verification_uri_complete that is not https (while the instance base URL is https), is otherwise unparseable/unsecure per secureLoginURL, or contains a URL fragment (#...).","commonSituations":"Misconfigured private Auth0/identity provider serving http verification URLs; a reverse proxy or custom domain injecting a fragment; an instance base URL given as http:// while the returned URL needs https; a proxy rewriting the verification URL.","solutions":["Check that the private instance's authorization server returns an https verification_uri with no fragment","If the instance intentionally runs over http, pass --instance with the http:// base URL so http verification URLs are accepted","Inspect the device-authorization endpoint response (curl the /oauth/device/code endpoint) to see the exact URL returned","Fix the identity provider / proxy configuration so it emits a clean https verification URL"],"exampleFix":"// before\nconst instance = \"http://my-instance.example.com\"; // https server, http base disables the https requirement incorrectly\n// after\nconst instance = \"https://my-instance.example.com\"; // and ensure the provider returns an https verification_uri without #fragment","handlingStrategy":"validation","validationCode":"const u = new URL(uri);\nif (u.protocol !== \"https:\" || u.hash) throw new Error(\"verification URL must be https and fragment-free\");","typeGuard":"function isSecureVerificationURL(u) { return u.protocol === \"https:\" && !u.hash && u.hostname.length > 0; }","tryCatchPattern":"try { await login({ instance }) } catch (e) { if (e.message.includes(\"unsafe browser URL\")) console.error(\"Instance returned a non-https or fragment-laden verification URL; fix the IdP config\"); }","preventionTips":["Serve verification URLs over https from the identity provider","Never append fragments (#) to verification_uri_complete","Keep the instance base URL protocol in sync with the verification URL protocol"],"tags":["oauth","device-flow","url-validation","security"],"backgroundTag":"invalid-url","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}