transact-rs/sqlx · error · io::Error (InvalidData)

filename passed to SQLite must not contain nul bytes

Error message

filename passed to SQLite must not contain nul bytes

What it means

After appending query parameters, from_options converts the filename string into a CString for the SQLite C API. CString::new fails if the string embeds an interior NUL byte, so sqlx maps that failure to this InvalidData error. An embedded nul in the filename would truncate the path at the C boundary, so sqlx refuses it.

Source

Thrown at sqlx-sqlite/src/connection/establish.rs:108

        if let Some(vfs) = options.vfs.as_deref() {
            query_params.insert("vfs", vfs);
        }

        if !query_params.is_empty() {
            filename = format!(
                "file:{}?",
                percent_encoding::percent_encode(filename.as_bytes(), NON_ALPHANUMERIC),
            );

            // Suffix serializer automatically handles `&` separators for us.
            let filename_len = filename.len();
            filename = form_urlencoded::Serializer::for_suffix(filename, filename_len)
                .extend_pairs(query_params)
                .finish();
        }

        let filename = CString::new(filename).map_err(|_| {
            io::Error::new(
                io::ErrorKind::InvalidData,
                "filename passed to SQLite must not contain nul bytes",
            )
        })?;

        #[cfg(feature = "load-extension")]
        let extensions = options
            .extensions
            .iter()
            .map(|(name, entry)| {
                let entry = entry
                    .as_ref()
                    .map(|e| {
                        CString::new(e.as_bytes()).map_err(|_| {
                            io::Error::new(
                                io::ErrorKind::InvalidData,
                                "extension entrypoint names passed to SQLite must not contain nul bytes"
                            )

View on GitHub (pinned to 03af8bcc57)

Solutions

  1. Sanitize/validate the filename before building options: reject or strip any '\0' characters
  2. Trim the value at the first NUL if the trailing content is known garbage (filename.split('\0').next())
  3. Fix the source of the path (config file, env var, DB row) that embedded the nul byte
  4. Add an input-validation error upstream so users get a clearer message than the connection failure

Example fix

// before
let opts = SqliteConnectOptions::new().filename(raw_name); // raw_name may contain '\0'
let pool = SqlitePool::connect_with(opts).await?;
// after
let clean = raw_name.split('\0').next().context("filename contained nul byte")?;
let pool = SqlitePool::connect(&format!("sqlite://{clean}")).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_no_nul(s: &str) -> Result<&str, String> {
    if s.contains('\0') {
        Err(format!("SQLite filename contains nul byte: {:?}", s))
    } else {
        Ok(s)
    }
}
let filename = ensure_no_nul(&raw_name)?;
let opts = SqliteConnectOptions::new().filename(filename);

Type guard

fn is_nul_free(s: &str) -> bool {
    !s.as_bytes().contains(&b'\0')
}

Try / catch

match SqlitePool::connect_with(opts).await {
    Ok(pool) => Ok(pool),
    Err(e) if e.to_string().contains("must not contain nul bytes") => {
        Err(anyhow!("database path contains a nul byte; check the configuration source"))
    }
    Err(e) => Err(e.into()),
}

Prevention

When it happens

Trigger: Connecting to SQLite with a SqliteConnectOptions whose filename (or a query-param value appended to it, e.g. mode/cache options derived from user input) contains a '\0' character, producing CString::new failure.

Common situations: Paths or options read from binary input, log files, or user forms containing literal NUL characters; truncated C-string data copied into a Rust String; malicious or corrupt configuration values.

Related errors


AI-assisted analysis of transact-rs/sqlx@03af8bcc57 (2026-09-03). Data as JSON: /api/errors/edfcd16971ea44ac. Report an issue: GitHub.