{"record":{"id":"61c7156428685877","repo":"Hmbown/CodeWhale","slug":"outbound-origin-must-not-embed-credentials","errorCode":null,"errorMessage":"outbound origin must not embed credentials","messagePattern":"outbound origin must not embed credentials","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/cloud_dispatch.rs","lineNumber":1267,"sourceCode":"/// - 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) {\n            return Ok(url);\n        }\n        bail!(\"loopback origins are not allowed in release builds\");","sourceCodeStart":1249,"sourceCodeEnd":1285,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/cloud_dispatch.rs#L1249-L1285","documentation":"The origin validator rejects URLs carrying userinfo (user:pass@host) so credentials never ride along inside the origin itself. Credentials must be supplied out of band (headers/env), not embedded in the URL.","triggerScenarios":"Configuring an origin like https://user:token@api.example.com or https://admin@host/ as the remote endpoint or toolbox URL.","commonSituations":"Copying a URL that worked in a browser/curl with embedded basic-auth; legacy tooling that expected credentials in the URL; secrets committed into config files.","solutions":["Remove the user:password@ portion from the URL and pass the credential via the appropriate header or env var.","Check env var contents: strip any '@'-style credential suffix before setting DAYTONA_API_URL.","Move secrets out of config files into the credential store / environment."],"exampleFix":"// before\nexport DAYTONA_API_URL=https://user:token@api.example.com\n// after\nexport DAYTONA_API_URL=https://api.example.com\nexport DAYTONA_API_KEY=token","handlingStrategy":"validation","validationCode":"fn has_userinfo(raw: &str) -> bool {\n    reqwest::Url::parse(raw.trim()).map(|u| !u.username().is_empty() || u.password().is_some()).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"if has_userinfo(raw) {\n    eprintln!(\"strip user:pass@ from the origin; pass credentials via headers/env\");\n}","preventionTips":["Never embed credentials in URLs; use env vars or a credential store.","Sanitize copied URLs of '@' userinfo before saving config.","Audit config files for basic-auth-style URLs."],"tags":["validation","ssrf","credentials","url"],"backgroundTag":"missing-credentials","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"}