{"record":{"id":"b47982477781cbbc","repo":"astrid-runtime/astrid","slug":"cors-origin-raw-doesn-t-parse-as-a-url-e","errorCode":null,"errorMessage":"CORS origin {raw:?} doesn't parse as a URL: {e}","messagePattern":"CORS origin (.+?) doesn't parse as a URL: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-gateway/src/config.rs","lineNumber":189,"sourceCode":"                    tls.cert_path.display()\n                );\n            }\n            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() != \"/\" {","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/config.rs#L171-L207","documentation":"validate_cors_origin parses each configured CORS origin as a URL and rejects values url::Url::parse cannot handle. Origins are byte-matched against Access-Control-Allow-Origin, so a malformed string would silently never match a real preflight; config validation fails fast instead.","triggerScenarios":"A CORS origin entry in config (validated via validate()) that is not a syntactically valid URL — e.g. missing scheme, spaces, stray characters — causing url::Url::parse to return a RelativeUrlWithoutBase or similar ParseError.","commonSituations":"Writing 'app.example.com' without https://; typos like 'https//app.example.com'; pasting origins with trailing whitespace; env-var interpolation producing an empty or partial value.","solutions":["Fix the origin string in config to a fully-qualified absolute URL (scheme + host)","Ensure the scheme is included: 'https://app.example.com' not 'app.example.com'","Trim whitespace from the configured value before validation","Re-run config validation to confirm all origins parse"],"exampleFix":"// before\ncors_origins = [\"app.example.com\", \"https://dashboard.example.io\"]\n// after\ncors_origins = [\"https://app.example.com\", \"https://dashboard.example.io\"]","handlingStrategy":"validation","validationCode":"fn is_valid_cors_origin(raw: &str) -> bool {\n    match url::Url::parse(raw.trim()) {\n        Ok(u) => matches!(u.scheme(), \"http\" | \"https\"),\n        Err(_) => false,\n    }\n}\n// assert all origins pass before loading config:\n// config.cors_origins.iter().all(|o| is_valid_cors_origin(o))","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always write origins as absolute URLs including scheme (https://...)","Trim whitespace from config values sourced from env/files","Run config validation in CI so bad origins fail before deploy"],"tags":["cors","config","url"],"backgroundTag":"invalid-url-format","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}