{"record":{"id":"faf3197641cbcea9","repo":"JuliusBrussee/caveman","slug":"instance-requires-a-private-https-origin-http-loopback-is","errorCode":null,"errorMessage":"--instance requires a private HTTPS origin (HTTP loopback is allowed for local development)","messagePattern":"--instance requires a private HTTPS origin \\(HTTP loopback is allowed for local development\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":9587,"sourceCode":"      if (noBrowser) commandUsage(usage);\n      noBrowser = true;\n      continue;\n    }\n    const flag = arg.split(\"=\", 1)[0]!;\n    if ([\"--instance\", \"--base-url\", \"--gateway-url\"].includes(flag)) {\n      const value = arg.includes(\"=\") ? arg.slice(arg.indexOf(\"=\") + 1) : argv[++index];\n      if (!value || value.startsWith(\"-\") || values.has(flag)) commandUsage(usage);\n      values.set(flag, value);\n      continue;\n    }\n    commandUsage(usage);\n  }\n  const instance = values.get(\"--instance\");\n  if (instance === undefined) return { noBrowser };\n  if (values.has(\"--base-url\") || values.has(\"--gateway-url\")) commandUsage(usage);\n  const url = new URL(instance);\n  if (!secureLoginURL(url) || url.pathname !== \"/\" || url.search || url.hash || url.hostname.replace(/\\.$/, \"\") === new URL(PROD_API_URL).hostname) {\n    throw new Error(\"--instance requires a private HTTPS origin (HTTP loopback is allowed for local development)\");\n  }\n  return { noBrowser, instance: url.origin };\n}\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\");","sourceCodeStart":9569,"sourceCodeEnd":9605,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L9569-L9605","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass only the bare origin over HTTPS: --instance https://caveman.internal.example (no path, query, hash, or credentials).","For local development use HTTP loopback explicitly: --instance http://localhost:8787 or http://127.0.0.1:8787.","Strip credentials from the URL and provide them another way, and remove any path/query/fragment from the value before retrying."],"exampleFix":"// before\ncaveman login --instance http://caveman.staging.example:8443/dashboard?tenant=acme   // throws\n// after\ncaveman login --instance https://caveman.staging.example","handlingStrategy":"validation","validationCode":"function validateInstanceUrl(raw) {\n  const u = new URL(raw);\n  const isLoopbackHttp = u.protocol === \"http:\" && [\"localhost\", \"127.0.0.1\", \"::1\"].includes(u.hostname.replace(/\\.$/, \"\"));\n  const ok = !u.username && !u.password && u.pathname === \"/\" && !u.search && !u.hash && (u.protocol === \"https:\" || isLoopbackHttp);\n  if (!ok) throw new Error(\"--instance must be a bare private HTTPS origin (HTTP loopback allowed).\");\n  return u.origin;\n}","typeGuard":null,"tryCatchPattern":"try {\n  caveman.login({ instance });\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"--instance requires a private HTTPS origin\")) {\n    console.error(\"Pass only the origin: https://host (or http://localhost:port for dev). No path/query/hash/credentials.\");\n  } else throw e;\n}","preventionTips":["Copy only the scheme+host(+port) of your private deployment into --instance, never the full browser URL.","Use http://localhost:<port> explicitly for local development; anything else must be HTTPS.","Never embed user:password credentials in the instance URL."],"tags":["url-validation","security","https","cli-flag","login"],"backgroundTag":"invalid-url-format","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"}