{"record":{"id":"386a9755c7985c77","repo":"astrid-runtime/astrid","slug":"cors-origin-raw-carries-a-query-fragment-orig","errorCode":null,"errorMessage":"CORS origin {raw:?} carries a query/fragment; origins are scheme+host+port only","messagePattern":"CORS origin (.+?) carries a query/fragment; origins are scheme\\+host\\+port only","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-gateway/src/config.rs","lineNumber":214,"sourceCode":"    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()\n        );\n    }\n    if parsed.query().is_some() || parsed.fragment().is_some() {\n        anyhow::bail!(\n            \"CORS origin {raw:?} carries a query/fragment; origins are scheme+host+port only\"\n        );\n    }\n    // Disallow trailing-slash forms — browsers send `https://app.example`\n    // (no slash) in `Origin:` and the response header is byte-matched.\n    if raw.ends_with('/') {\n        anyhow::bail!(\n            \"CORS origin {raw:?} has a trailing slash; remove it (browsers send `Origin:` without one)\"\n        );\n    }\n    // Reject a raw IDN — browsers transmit the Punycode (ASCII)\n    // form in `Origin:`, so the bytes wouldn't match anyway. The\n    // `Url` parser already normalizes the host to its ASCII form on\n    // parse; if the *raw* string contained a non-ASCII character,\n    // the parsed `origin()` ASCII-serialization won't equal `raw`.\n    let parsed_ascii = parsed.origin().ascii_serialization();\n    if parsed_ascii != raw {\n        anyhow::bail!(","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/config.rs#L196-L232","documentation":"CORS origin lint in validate_cors_origin: the configured origin URL carries a non-empty path (and/or query/fragment). Browsers send `Origin:` as scheme+host+port only, so an origin with a path can never match a real preflight and would silently never allow the intended site; validation rejects it so operators fix the config instead of debugging failed CORS at runtime.","triggerScenarios":"Thrown at crates/astrid-gateway/src/config.rs:214 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Remove the ?query and #fragment from the origin entry","Keep only scheme://host[:port] in cors_allow_origins","Fix the config source (template or script) that appended the extra parts"],"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"}