astrid-runtime/astrid · error

CORS origin has no host component

Error message

CORS origin {raw:?} has no host component

What it means

Validation guard in validate_cors_origin: the origin string parsed as a URL but has no host component (e.g. 'file:///path' or a scheme-only value), so it cannot be byte-matched against a browser Origin header.

Solutions

  1. Add the host to the entry, e.g. https://app.example.com
  2. Remove file:// or scheme-only values from cors_allow_origins
  3. Verify the config template expanded the hostname placeholder correctly
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at crates/astrid-gateway/src/config.rs:197 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/276070e0d20ebb19. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-gateway/src/config.rs:197

/// Validate a single CORS origin string. Origins MUST be of the form
/// `scheme://host[:port]` with no path, query, or fragment — that's
/// what the browser sends in `Origin:` and what the response's
/// `Access-Control-Allow-Origin:` is byte-matched against. A
/// `https://app.example/` (trailing slash) would silently fail to
/// match a real preflight; rejecting it here is what makes that
/// surfacable.
fn validate_cors_origin(raw: &str) -> anyhow::Result<()> {
    let parsed = url::Url::parse(raw)
        .map_err(|e| anyhow::anyhow!("CORS origin {raw:?} doesn't parse as a URL: {e}"))?;
    match parsed.scheme() {
        "http" | "https" => {},
        other => anyhow::bail!(
            "CORS origin {raw:?} uses scheme {other:?}; only http/https are valid for browser origins"
        ),
    }
    if parsed.host_str().is_none() {
        anyhow::bail!("CORS origin {raw:?} has no host component");
    }
    // Browsers strip userinfo before sending `Origin:`, so a config
    // entry with embedded credentials can never match a real
    // preflight. Reject so operators don't silently misconfigure.
    if !parsed.username().is_empty() || parsed.password().is_some() {
        anyhow::bail!(
            "CORS origin {raw:?} carries userinfo (user:password); browsers strip it before sending `Origin:` so this can never match"
        );
    }
    if parsed.path() != "" && parsed.path() != "/" {
        anyhow::bail!(
            "CORS origin {raw:?} carries a path ({:?}); origins are scheme+host+port only",
            parsed.path()
        );
    }
    if parsed.query().is_some() || parsed.fragment().is_some() {
        anyhow::bail!(
            "CORS origin {raw:?} carries a query/fragment; origins are scheme+host+port only"

View on GitHub (pinned to affd8760f4)