{"record":{"id":"851647a63168ab7f","repo":"gitbutlerapp/gitbutler","slug":"url-type-is-not-from-a-trusted-gitbutler-domain-url","errorCode":null,"errorMessage":"{url_type} is not from a trusted GitButler domain: {url}","messagePattern":"(.+?) is not from a trusted GitButler domain: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-installer/src/release.rs","lineNumber":116,"sourceCode":"    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.\n///\n/// This is more permissive than API validation, allowing downloads from:\n/// - `gitbutler.com` (root domain)\n/// - Any `*.gitbutler.com` subdomain (e.g., `releases.gitbutler.com`, `cdn.gitbutler.com`)","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-installer/src/release.rs#L98-L134","documentation":"After the HTTPS check, validate_gitbutler_url parses the URL and checks its host against a domain allowlist predicate (validate_api_url and validate_download_url each supply one). This error means the URL points at a host outside GitButler's trusted domains, blocking potential malicious redirects or misconfiguration.","triggerScenarios":"A download/API URL whose hostname is not in the allowlist: custom mirror, attacker-controlled redirect target, typo'd domain (e.g. gitbutlr.com), or test/staging host.","commonSituations":"Open-redirect responses from the release API, DNS hijacking or proxy rewriting hosts, developer configuring an unofficial mirror, DNS rebinding during incident testing.","solutions":["Use the official GitButler API/download domains as configured by the installer","Fix typos in a custom-configured host","Investigate if a redirect changed the host unexpectedly (possible security issue)","If you legitimately need another host, the allowlist predicates must be updated upstream"],"exampleFix":"null","handlingStrategy":"validation","validationCode":"fn is_trusted_host(url: &str) -> bool {\n    url::Url::parse(url).ok()\n        .and_then(|u| u.host_str().map(|h| h.to_string()))\n        .map(|h| h == \"api.gitbutler.com\" || h.ends_with(\".gitbutler.com\"))\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"trusted GitButler domain\") => {\n        // treat as potential security issue: log host, refuse custom mirrors\n    }\n    other => other?,\n}","preventionTips":["Only use official GitButler domains","Double-check hostname spelling in any custom config","Treat unexpected host changes after redirects as security signals","Keep the domain allowlist maintained upstream"],"tags":["security","url","domain","validation"],"backgroundTag":"invalid-url","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}