clockworklabs/SpacetimeDB · critical

Invalid header name from host

Error message

Invalid header name from host

What it means

On the host-to-guest HTTP bridge, header names arriving from the host are converted with http::HeaderName::from_bytes, which accepts only HTTP token characters. A name containing spaces, control bytes, or non-ASCII characters, or an empty name, is rejected - and this .expect converts that rejection into a panic that aborts the call.

Source

Thrown at crates/bindings/src/http.rs:653

    let request = http::Request::builder()
        .method(method)
        .uri(http::Uri::from_str(&uri).expect("Invalid URI from host"))
        .body(Body::from_bytes(body))
        .expect("Failed to build request");

    let (mut parts, body) = request.into_parts();
    parts.version = match version {
        st_http::Version::Http09 => http::Version::HTTP_09,
        st_http::Version::Http10 => http::Version::HTTP_10,
        st_http::Version::Http11 => http::Version::HTTP_11,
        st_http::Version::Http2 => http::Version::HTTP_2,
        st_http::Version::Http3 => http::Version::HTTP_3,
    };
    parts.headers = headers
        .into_iter()
        .map(|(k, v)| {
            let name = http::HeaderName::from_bytes(k.as_bytes()).expect("Invalid header name from host");
            let value = http::HeaderValue::from_bytes(v.as_ref()).expect("Invalid header value from host");
            (name, value)
        })
        .collect();

    http::Request::from_parts(parts, body)
}

#[cfg(feature = "unstable")]
pub(crate) fn response_into_wire(response: http::Response<Body>) -> (st_http::Response, Bytes) {
    let (parts, body) = response.into_parts();
    let st_response = st_http::Response {
        headers: parts
            .headers
            .into_iter()
            .map(|(k, v)| (k.map(|k| k.as_str().into()), v.as_bytes().into()))
            .collect(),
        version: match parts.version {

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Sanitize header names to ASCII token characters before they reach the bridge; drop invalid ones with a logged warning.
  2. Validate and reject malformed headers (HTTP 400) at your API boundary instead of letting them reach the module.
  3. Upgrade bindings - newer versions may return an error instead of panicking.

Example fix

// before: panics on malformed input
let name = http::HeaderName::from_bytes(k.as_bytes()).expect("Invalid header name from host");

// after: validate and skip
let Ok(name) = http::HeaderName::from_bytes(k.as_bytes()) else {
    log::warn!("dropping invalid header name: {k:?}");
    continue;
};
Defensive patterns

Strategy: type-guard

Validate before calling

// Reject malformed header names at your boundary before they are forwarded:
fn header_name_ok(name: &str) -> bool {
    !name.is_empty()
        && name.bytes().all(|b| matches!(b, 33..=126) && !b"()<>@,;:\\"/[]?={}".contains(&b))
        && b' ' != 32 // spaces excluded by the 33..=126 range above
}

Type guard

fn valid_header_name(name: &str) -> bool {
    !name.is_empty() && name.bytes().all(|b| b > 32 && b < 127 && !b"()<>@,;:\\"/[]?={}".contains(&b))
}

Try / catch

// It panics, not returns Err: catch it at the embedding boundary.
let outcome = std::panic::catch_unwind(|| handle_request(request));
if outcome.is_err() {
    // Trap already unwound this call: log the offending headers, respond 400/500,
    // and keep the process alive.
}

Prevention

When it happens

Trigger: The host supplies a request whose header name is malformed: a space instead of a hyphen ("Content Type"), non-ASCII/UTF-8 names, control characters, or the empty string - typically unvalidated client or gateway input forwarded into the module's HTTP API.

Common situations: Proxies/gateways injecting malformed headers; header names built from unvalidated user input; fuzzed HTTP requests reaching the bridge.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/1fd6ddee367ea75d. Report an issue: GitHub.