JuliusBrussee/caveman · error

--instance requires a private HTTPS origin (HTTP loopback…

Error message

--instance requires a private HTTPS origin (HTTP loopback is allowed for local development)

What it means

The `--instance` flag of the login command selects a private/self-hosted Caveman deployment against which to authenticate. For safety the URL must be a private HTTPS origin with no credentials, exactly the root path, no query string or hash, and must not equal the production API hostname; plain HTTP is only tolerated for loopback development. Anything failing secureLoginURL or these extra shape checks makes the CLI throw this error instead of opening a browser to a potentially hostile endpoint.

Solutions

  1. Pass only the bare origin over HTTPS: --instance https://caveman.internal.example (no path, query, hash, or credentials).
  2. For local development use HTTP loopback explicitly: --instance http://localhost:8787 or http://127.0.0.1:8787.
  3. Strip credentials from the URL and provide them another way, and remove any path/query/fragment from the value before retrying.

Example fix

// before
caveman login --instance http://caveman.staging.example:8443/dashboard?tenant=acme   // throws
// after
caveman login --instance https://caveman.staging.example
Defensive patterns

Strategy: validation

Validate before calling

function validateInstanceUrl(raw) {
  const u = new URL(raw);
  const isLoopbackHttp = u.protocol === "http:" && ["localhost", "127.0.0.1", "::1"].includes(u.hostname.replace(/\.$/, ""));
  const ok = !u.username && !u.password && u.pathname === "/" && !u.search && !u.hash && (u.protocol === "https:" || isLoopbackHttp);
  if (!ok) throw new Error("--instance must be a bare private HTTPS origin (HTTP loopback allowed).");
  return u.origin;
}

Try / catch

try {
  caveman.login({ instance });
} catch (e) {
  if (e instanceof Error && e.message.startsWith("--instance requires a private HTTPS origin")) {
    console.error("Pass only the origin: https://host (or http://localhost:port for dev). No path/query/hash/credentials.");
  } else throw e;
}

Prevention

When it happens

Trigger: Running the login command with --instance set to: an http:// URL on a non-loopback host; a URL containing userinfo (user:pass@); a URL with a path other than "/" (e.g. https://caveman.internal/api), a query string (?x=1) or a fragment (#/dash); or the production API hostname passed explicitly.

Common situations: Pasting the full dashboard URL (with path/query) instead of just the origin; pointing --instance at a staging HTTP endpoint; including basic-auth credentials in the URL; forgetting that only loopback HTTP is allowed for local development.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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

Appendix: source

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

      if (noBrowser) commandUsage(usage);
      noBrowser = true;
      continue;
    }
    const flag = arg.split("=", 1)[0]!;
    if (["--instance", "--base-url", "--gateway-url"].includes(flag)) {
      const value = arg.includes("=") ? arg.slice(arg.indexOf("=") + 1) : argv[++index];
      if (!value || value.startsWith("-") || values.has(flag)) commandUsage(usage);
      values.set(flag, value);
      continue;
    }
    commandUsage(usage);
  }
  const instance = values.get("--instance");
  if (instance === undefined) return { noBrowser };
  if (values.has("--base-url") || values.has("--gateway-url")) commandUsage(usage);
  const url = new URL(instance);
  if (!secureLoginURL(url) || url.pathname !== "/" || url.search || url.hash || url.hostname.replace(/\.$/, "") === new URL(PROD_API_URL).hostname) {
    throw new Error("--instance requires a private HTTPS origin (HTTP loopback is allowed for local development)");
  }
  return { noBrowser, instance: url.origin };
}

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

View on GitHub (pinned to 3ee70a1026)