{"record":{"id":"6ce2326dc7115011","repo":"Hmbown/CodeWhale","slug":"outbound-origin-must-be-http-or-https","errorCode":null,"errorMessage":"outbound origin must be http or https","messagePattern":"outbound origin must be http or https","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/cloud_dispatch.rs","lineNumber":1264,"sourceCode":"///\n/// Rules:\n/// - `https` only for public hosts.\n/// - explicit loopback hosts (`localhost`, `127.0.0.1`, `::1`) are allowed\n///   only in debug builds, as the escape hatch for local smoke tests against\n///   a self-hosted sandbox service; release builds reject them outright.\n/// - the host must not be a private / link-local / reserved / multicast\n///   address or a `.local` / `.internal` name, and no userinfo may ride\n///   along.\n///\n/// DNS-resolved rebinding is out of scope and documented as such.\npub fn validate_outbound_origin(raw: &str) -> Result<reqwest::Url> {\n    let trimmed = raw.trim();\n    if trimmed.is_empty() || trimmed.len() > MAX_REMOTE_BYTES {\n        bail!(\"outbound origin is empty or oversized\");\n    }\n    let url = reqwest::Url::parse(trimmed).context(\"outbound origin is not a valid URL\")?;\n    if !matches!(url.scheme(), \"http\" | \"https\") {\n        bail!(\"outbound origin must be http or https\");\n    }\n    if !url.username().is_empty() || url.password().is_some() {\n        bail!(\"outbound origin must not embed credentials\");\n    }\n    let host = url\n        .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) {","sourceCodeStart":1246,"sourceCodeEnd":1282,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/cloud_dispatch.rs#L1246-L1282","documentation":"validate_outbound_origin only accepts http/https schemes; any other scheme (file:, ftp:, ws:, etc.) is rejected before any request is built. This closes the SSRF door on non-HTTP transports for credential-bearing calls.","triggerScenarios":"Configuring an endpoint like file:///etc/passwd, ftp://host/x, or ws://... as the outbound origin (DAYTONA_API_URL, remote endpoint, or sandbox toolbox_url).","commonSituations":"Copy-pasting a websocket or internal file URL into a remote-endpoint setting; typo'd scheme like htp:// still parses via other schemes handling.","solutions":["Change the configured origin to start with https:// (http is only useful for loopback in debug builds).","Strip custom scheme prefixes (ws://, file://) from the config value before validation.","Verify which env/config field you set — you may have populated the wrong variable."],"exampleFix":"// before\nexport DAYTONA_API_URL=ftp://api.example.com\n// after\nexport DAYTONA_API_URL=https://api.example.com","handlingStrategy":"validation","validationCode":"if !raw.trim().starts_with(\"https://\") { return Err(\"origin must start with https://\"); }","typeGuard":null,"tryCatchPattern":"if let Err(e) = validate_outbound_origin(raw) {\n    if e.to_string().contains(\"http or https\") {\n        eprintln!(\"fix the scheme of the configured origin: {raw}\");\n    }\n}","preventionTips":["Always write origins with explicit https:// in config.","Add a config-load-time scheme check.","Avoid copy-pasting non-http URL schemes into endpoint settings."],"tags":["validation","ssrf","url-scheme","config"],"backgroundTag":"invalid-url","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}