JuliusBrussee/caveman · error

private device authorization returned an unsafe browser URL

Error message

private device authorization returned an unsafe browser URL

What it means

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.

Solutions

  1. Check that the private instance's authorization server returns an https verification_uri with no fragment
  2. If the instance intentionally runs over http, pass --instance with the http:// base URL so http verification URLs are accepted
  3. Inspect the device-authorization endpoint response (curl the /oauth/device/code endpoint) to see the exact URL returned
  4. Fix the identity provider / proxy configuration so it emits a clean https verification URL

Example fix

// before
const instance = "http://my-instance.example.com"; // https server, http base disables the https requirement incorrectly
// after
const instance = "https://my-instance.example.com"; // and ensure the provider returns an https verification_uri without #fragment
Defensive patterns

Strategy: validation

Validate before calling

const u = new URL(uri);
if (u.protocol !== "https:" || u.hash) throw new Error("verification URL must be https and fragment-free");

Type guard

function isSecureVerificationURL(u) { return u.protocol === "https:" && !u.hash && u.hostname.length > 0; }

Try / catch

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"); }

Prevention

When it happens

Trigger: 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 (#...).

Common situations: 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.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/df00aff19c5b921a. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:9608

}

function secureLoginURL(url: URL, allowLoopback = true): boolean {
  return !url.username && !url.password && (url.protocol === "https:" ||
    (allowLoopback && url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)));
}

function privateVerificationURL(code: Record<string, unknown>, instance: string): string {
  if (typeof code.device_code !== "string" || !code.device_code || code.device_code.length > 4096 ||
      typeof code.user_code !== "string" || !/^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/.test(code.user_code) ||
      typeof code.expires_in !== "number" || !Number.isFinite(code.expires_in) || code.expires_in <= 0 || code.expires_in > 3600 ||
      (code.interval !== undefined && (typeof code.interval !== "number" || !Number.isFinite(code.interval) || code.interval < 0 || code.interval > 60))) {
    throw new Error("private device authorization returned an invalid code response");
  }
  const value = code.verification_uri_complete ?? code.verification_uri;
  if (typeof value !== "string") throw new Error("private device authorization omitted its browser URL");
  const url = new URL(value);
  if (!secureLoginURL(url, new URL(instance).protocol === "http:") || url.hash) {
    throw new Error("private device authorization returned an unsafe browser URL");
  }
  url.searchParams.set("user_code", code.user_code);
  url.searchParams.set("connection", "mcp");
  url.searchParams.set("client_name", "Caveman CLI");
  return url.href;
}

function openLoginBrowser(url: string): void {
  const opener = loginBrowserOpener(url);
  if (!which(opener.command)) {
    process.stderr.write(`  browser opener unavailable; open ${url}\n`);
    return;
  }
  const child = spawn(opener.command, opener.args, { detached: true, stdio: "ignore", windowsHide: true });
  child.once("error", () => process.stderr.write(`  browser did not open; open ${url}\n`));
  child.unref();
}

View on GitHub (pinned to 3ee70a1026)