{"record":{"id":"7527cd63fcc73420","repo":"astrid-runtime/astrid","slug":"cors-origin-raw-uses-scheme-other-only-ht","errorCode":null,"errorMessage":"CORS origin {raw:?} uses scheme {other:?}; only http/https are valid for browser origins","messagePattern":"CORS origin (.+?) uses scheme (.+?); only http/https are valid for browser origins","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-gateway/src/config.rs","lineNumber":192,"sourceCode":"            crate::tls::warn_if_key_is_too_open(&tls.key_path);\n        }\n        Ok(())\n    }\n}\n\n/// Validate a single CORS origin string. Origins MUST be of the form\n/// `scheme://host[:port]` with no path, query, or fragment — that's\n/// what the browser sends in `Origin:` and what the response's\n/// `Access-Control-Allow-Origin:` is byte-matched against. A\n/// `https://app.example/` (trailing slash) would silently fail to\n/// match a real preflight; rejecting it here is what makes that\n/// surfacable.\nfn validate_cors_origin(raw: &str) -> anyhow::Result<()> {\n    let parsed = url::Url::parse(raw)\n        .map_err(|e| anyhow::anyhow!(\"CORS origin {raw:?} doesn't parse as a URL: {e}\"))?;\n    match parsed.scheme() {\n        \"http\" | \"https\" => {},\n        other => anyhow::bail!(\n            \"CORS origin {raw:?} uses scheme {other:?}; only http/https are valid for browser origins\"\n        ),\n    }\n    if parsed.host_str().is_none() {\n        anyhow::bail!(\"CORS origin {raw:?} has no host component\");\n    }\n    // Browsers strip userinfo before sending `Origin:`, so a config\n    // entry with embedded credentials can never match a real\n    // preflight. Reject so operators don't silently misconfigure.\n    if !parsed.username().is_empty() || parsed.password().is_some() {\n        anyhow::bail!(\n            \"CORS origin {raw:?} carries userinfo (user:password); browsers strip it before sending `Origin:` so this can never match\"\n        );\n    }\n    if parsed.path() != \"\" && parsed.path() != \"/\" {\n        anyhow::bail!(\n            \"CORS origin {raw:?} carries a path ({:?}); origins are scheme+host+port only\",\n            parsed.path()","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/config.rs#L174-L210","documentation":"Validation guard in validate_cors_origin (run per cors_allow_origins entry at boot): the origin string parsed as a URL but its scheme is not http or https, so it can never match a browser Origin header, which only ever carries those schemes.","triggerScenarios":"Thrown at crates/astrid-gateway/src/config.rs:192 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Change the cors_allow_origins entry to an http:// or https:// origin","Remove ws:// or custom-scheme entries — WebSocket handshakes still originate from http(s) pages","Drop non-URL entries like '*' and use the dedicated allow-all mechanism if intended"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}