{"record":{"id":"d845cbda48c23bbf","repo":"gitbutlerapp/gitbutler","slug":"no-host-in-url","errorCode":null,"errorMessage":"No host in {} URL","messagePattern":"No host in (.+?) URL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-installer/src/release.rs","lineNumber":112,"sourceCode":"/// Common URL validation logic for GitButler domains.\n///\n/// Validates HTTPS protocol, parses URL, and checks the host against a predicate.\nfn validate_gitbutler_url(\n    url: &str,\n    url_type: &str,\n    is_host_valid: impl Fn(&str) -> bool,\n) -> Result<()> {\n    // Only allow HTTPS URLs\n    if !url.starts_with(\"https://\") {\n        bail!(\"{url_type} must use HTTPS: {url}\");\n    }\n\n    // Extract host from URL\n    let url_parsed =\n        url::Url::parse(url).with_context(|| format!(\"Invalid {} URL\", url_type.to_lowercase()))?;\n    let host = url_parsed\n        .host_str()\n        .ok_or_else(|| anyhow!(\"No host in {} URL\", url_type.to_lowercase()))?;\n\n    // Validate host using the provided predicate\n    if !is_host_valid(host) {\n        bail!(\"{url_type} is not from a trusted GitButler domain: {url}\");\n    }\n\n    Ok(())\n}\n\n/// Validates that an API URL is from the trusted API domain.\n///\n/// API endpoints should only be served from app.gitbutler.com to prevent\n/// redirecting API requests to other subdomains.\npub(crate) fn validate_api_url(url: &str) -> Result<()> {\n    validate_gitbutler_url(url, \"API URL\", |host| host == \"app.gitbutler.com\")\n}\n\n/// Validates that a download URL is from a trusted GitButler domain.","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-installer/src/release.rs#L94-L130","documentation":"Thrown by validate_gitbutler_url in crates/but-installer/src/release.rs when a URL parsed successfully with url::Url::parse but has no host component (host_str() == None). This happens for URLs like \"https:///path\" or scheme-only forms where no authority follows the scheme; the same function also enforces HTTPS and the trusted gitbutler.com host allowlist used for both API and download URLs.","triggerScenarios":"Passing an API or download URL that starts with https:// but lacks a host (\"https:///releases\"); a malformed custom endpoint or overriden URL reaching validate_api_url/validate_download_url; URL construction bugs that drop the host segment.","commonSituations":"A typo'd or template-substitution-broken URL (empty host variable) passed to the installer; env or config overrides that produce scheme-only URLs; test fixtures with hand-written malformed URLs.","solutions":["Fix the URL to include a host, e.g. https://app.gitbutler.com/releases.","Check where the URL came from — env overrides, config files, or code that built it — and fix the empty-host bug at the source.","If you need a custom endpoint, host it on a *.gitbutler.com domain or patch the validator; other hosts are rejected by design."],"exampleFix":"// before\nlet base = format!(\"https://{}/releases\", host_maybe_empty);\n\n// after\nlet host = std::env::var(\"GB_API_HOST\").unwrap_or_else(|_| \"app.gitbutler.com\".into());\nassert!(!host.is_empty(), \"API host must not be empty\");\nlet base = format!(\"https://{host}/releases\");","handlingStrategy":"validation","validationCode":"// Before passing any URL into the installer's validators\nfn url_has_https_host(url: &str) -> bool {\n    url::Url::parse(url).ok().and_then(|u| u.host_str().map(|h| u.scheme() == \"https\" && !h.is_empty())).unwrap_or(false)\n}","typeGuard":"fn is_valid_gitbutler_url(url: &str) -> bool {\n    match url::Url::parse(url) {\n        Ok(u) => matches!(u.host_str(), Some(h) if h == \"gitbutler.com\" || h.ends_with(\".gitbutler.com\")),\n        Err(_) => false,\n    }\n}","tryCatchPattern":null,"preventionTips":["Always build API/download URLs from a host constant plus path, never string templates with possibly-empty host variables.","Assert !host.is_empty() before formatting URLs.","Remember the allowlist only accepts *.gitbutler.com hosts over HTTPS — design custom endpoints accordingly."],"tags":["installer","url-validation","https","host-validation"],"backgroundTag":"invalid-url-host","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}