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
- 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
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
- 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
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
- private device login requires a keyless project grant with…
- awscreds: AWS_CONTAINER_CREDENTIALS_FULL_URI is not a valid…
- device authorization failed: HTTP
- device authorization failed: missing device code
- device credential delivery acknowledgement failed
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)