GitoxideLabs/gitoxide · error

git daemon virtual hosts must not contain NUL or LF

Error message

git daemon virtual hosts must not contain NUL or LF

What it means

Like the path check, the `git://` transport rejects virtual host names (the `virtual_host` argument) containing NUL (`\0`) or LF (`\n`), since these bytes cannot appear in the protocol's `host=` extra parameter. Fails with `InvalidInput` before any network I/O.

Solutions

  1. Trim the host string before passing it as a virtual host
  2. Validate the host contains only printable characters (no NUL/LF) at parse time
  3. Fix parsers that fail to strip line endings from config/input

Example fix

// before
let vhost = Some((line.to_string(), None)); // line ends with '\n'
// after
let vhost = Some((line.trim().to_string(), None));
Defensive patterns

Strategy: validation

Validate before calling

fn validate_vhost(host: &str) -> Result<(), String> {
    if host.bytes().any(|b| b == b'\0' || b == b'\n') {
        Err("virtual host contains NUL or LF".into())
    } else { Ok(()) }
}

Try / catch

match gix::connect(url, gix::protocol::transport::Protocol::Git) {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {
        eprintln!("check virtual host input: {e}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling `gix::connect()` with `Some((host, port))` as virtual host where `host` contains a NUL or newline byte.

Common situations: Parsing a virtual host from raw input (config lines, files) without trimming; string splitting bugs that keep a trailing newline in the host part.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/e257248677790e31. Report an issue: GitHub.

Appendix: source

Thrown at gix-transport/src/client/git/mod.rs:44

    use crate::{Protocol, Service};

    pub fn connect(
        service: Service,
        desired_version: Protocol,
        path: &[u8],
        virtual_host: Option<&(String, Option<u16>)>,
        extra_parameters: &[(&str, Option<&str>)],
    ) -> std::io::Result<BString> {
        let path = gix_url::expand_path::for_shell(path.into());
        let is_forbidden = |byte| matches!(byte, b'\0' | b'\n');
        if path.iter().copied().any(is_forbidden) {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "git daemon repository paths must not contain NUL or LF",
            ));
        }
        if virtual_host.is_some_and(|(host, _)| host.bytes().any(is_forbidden)) {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "git daemon virtual hosts must not contain NUL or LF",
            ));
        }

        let mut out = bstr::BString::from(service.as_str());
        out.push(b' ');
        out.extend_from_slice(&path);
        out.push(0);
        if let Some((host, port)) = virtual_host {
            out.push_str("host=");
            out.extend_from_slice(host.as_bytes());
            if let Some(port) = port {
                out.push_byte(b':');
                out.push_str(format!("{port}"));
            }
            out.push(0);
        }

View on GitHub (pinned to e73179060b)