astrid-runtime/astrid · error

CORS origin must be ASCII-only (Punycode); browsers send…

Error message

CORS origin {raw:?} must be ASCII-only (Punycode); browsers send the Punycoded form in `Origin:`. Use {parsed_ascii:?} instead.

What it means

Validation guard in validate_cors_origin: the entry is a raw IDN (non-ASCII hostname), but browsers transmit the Punycoded ASCII form in Origin:, so the raw bytes would never match; the error names the precomputed ASCII form to use instead.

Solutions

  1. Replace the IDN with the Punycoded ASCII origin given in the error message (e.g. https://xn--...:)
  2. Convert the hostname with an IDNA/punycode tool and re-enter it
  3. Verify the converted origin against a real browser preflight
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at crates/astrid-gateway/src/config.rs:232 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/8abdfcc6faa1e23b. Report an issue: GitHub.

Appendix: source

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

        anyhow::bail!(
            "CORS origin {raw:?} carries a query/fragment; origins are scheme+host+port only"
        );
    }
    // Disallow trailing-slash forms — browsers send `https://app.example`
    // (no slash) in `Origin:` and the response header is byte-matched.
    if raw.ends_with('/') {
        anyhow::bail!(
            "CORS origin {raw:?} has a trailing slash; remove it (browsers send `Origin:` without one)"
        );
    }
    // Reject a raw IDN — browsers transmit the Punycode (ASCII)
    // form in `Origin:`, so the bytes wouldn't match anyway. The
    // `Url` parser already normalizes the host to its ASCII form on
    // parse; if the *raw* string contained a non-ASCII character,
    // the parsed `origin()` ASCII-serialization won't equal `raw`.
    let parsed_ascii = parsed.origin().ascii_serialization();
    if parsed_ascii != raw {
        anyhow::bail!(
            "CORS origin {raw:?} must be ASCII-only (Punycode); browsers send the Punycoded form in `Origin:`. Use {parsed_ascii:?} instead."
        );
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn default_is_disabled() {
        let cfg = GatewayConfig::default();
        assert!(!cfg.enabled);
        assert_eq!(cfg.listen, "127.0.0.1:2787");
    }

    fn cfg_with_cors(origins: Vec<&str>) -> GatewayConfig {

View on GitHub (pinned to affd8760f4)