{"record":{"id":"23ba7bd5fac8b64e","repo":"Hmbown/CodeWhale","slug":"outbound-origin-must-be-a-public-service-host","errorCode":null,"errorMessage":"outbound origin must be a public service host","messagePattern":"outbound origin must be a public service host","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/cloud_dispatch.rs","lineNumber":1288,"sourceCode":"        .host_str()\n        .context(\"outbound origin has no host\")?\n        .trim_end_matches('.')\n        .to_ascii_lowercase();\n    // `Url::host_str` keeps IPv6 brackets; strip them for the checks below.\n    let host = host\n        .strip_prefix('[')\n        .and_then(|inner| inner.strip_suffix(']'))\n        .map(str::to_string)\n        .unwrap_or(host);\n    let loopback_name = host == \"localhost\" || host == \"127.0.0.1\" || host == \"::1\";\n    if loopback_name {\n        if cfg!(debug_assertions) {\n            return Ok(url);\n        }\n        bail!(\"loopback origins are not allowed in release builds\");\n    }\n    if host.ends_with(\".local\") || host.ends_with(\".internal\") {\n        bail!(\"outbound origin must be a public service host\");\n    }\n    if let Ok(ip) = host.parse::<std::net::IpAddr>() {\n        let blocked = match ip {\n            std::net::IpAddr::V4(v4) => {\n                let octets = v4.octets();\n                v4.is_loopback()\n                    || v4.is_private()\n                    || v4.is_link_local()\n                    || v4.is_unspecified()\n                    || v4.is_broadcast()\n                    || v4.is_multicast()\n                    || v4.is_documentation()\n                    // 100.64.0.0/10 (carrier-grade NAT, `is_shared` is\n                    // not stable yet)\n                    || (octets[0] == 100 && (octets[1] & 0b1100_0000) == 0b0100_0000)\n            }\n            std::net::IpAddr::V6(v6) => {\n                if let Some(v4) = v6.to_ipv4_mapped() {","sourceCodeStart":1270,"sourceCodeEnd":1306,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/cloud_dispatch.rs#L1270-L1306","documentation":"The validator requires the origin host to be a public service: names ending in .local (mDNS) or .internal (private DNS zones) are rejected because they only resolve on internal networks and defeat the public-host guarantee for credential-bearing calls.","triggerScenarios":"Setting the remote endpoint or toolbox URL to something like https://daytona.internal or https://toolbox.local.","commonSituations":"Corporate/internal cluster URLs pasted into client config; self-hosted deployments exposing only internal DNS names.","solutions":["Use the service's public hostname behind your gateway/proxy instead of the .local/.internal name.","Expose the sandbox service via a public https endpoint (or tunnel) and configure that origin.","For purely local testing, use debug builds with localhost rather than .internal names."],"exampleFix":"// before\nexport DAYTONA_API_URL=https://daytona.internal\n// after\nexport DAYTONA_API_URL=https://daytona.mycompany.example.com","handlingStrategy":"validation","validationCode":"let host = ... // extract host as in 765\nif host.ends_with(\".local\") || host.ends_with(\".internal\") { return Err(\"use a public hostname\"); }","typeGuard":null,"tryCatchPattern":"if let Err(e) = validate_outbound_origin(raw) {\n    if e.to_string().contains(\"public service host\") {\n        eprintln!(\".local/.internal hosts are rejected; expose the service publicly\");\n    }\n}","preventionTips":["Prefer public gateway hostnames over internal DNS names in client config.","Document the public endpoint for self-hosted deployments.","Validate origins at config load time so internal names fail early."],"tags":["validation","ssrf","dns","config"],"backgroundTag":"invalid-config-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T21:17:16.096Z"}