Hmbown/CodeWhale · error · io::Error

external credential path must name a regular file

Error message

external credential path must name a regular file

What it means

open_secure_regular_file performs TOCTOU-safe hardened opens of credential files. After opening the path it re-checks the metadata of the actually-opened handle and refuses anything that is not a regular file (e.g. a directory, FIFO, device, or socket). The library throws this to prevent reading credentials from special files or being tricked into following a swapped path.

Solutions

  1. Point the credential path at a real regular file (check with `file` or `stat -c %F <path>`).
  2. If the path is a directory, append the credential filename (e.g. /path/to/dir/token.json instead of /path/to/dir).
  3. Recreate the credential with a plain file: remove the FIFO/device and write the secret with a normal file write.
  4. Ensure no volume manager or container mount is substituting a device/pipe at that path.

Example fix

// before
let creds = read_codewhale_owned_to_string(Path::new("/home/me/.codewhale/credentials"))?; // path is a directory
// after
let creds = read_codewhale_owned_to_string(Path::new("/home/me/.codewhale/credentials/token.json"))?; // regular file
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_regular_file(path: &Path) -> std::io::Result<()> {
    let md = std::fs::metadata(path)?;
    if !md.file_type().is_file() {
        return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{} is not a regular file", path.display())));
    }
    Ok(())
}

Type guard

fn is_regular_file(path: &Path) -> bool {
    std::fs::metadata(path).map(|m| m.file_type().is_file()).unwrap_or(false)
}

Try / catch

match read_codewhale_owned_to_string(&path) {
    Ok(creds) => use(creds),
    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput && e.to_string().contains("regular file") => {
        eprintln!("credential path {} is not a regular file; fix the config", path.display());
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: read_to_string or read_codewhale_owned_to_string is given a path whose open handle's metadata reports a non-regular file type: a directory, named pipe, /dev node, or socket was passed as the credential path.

Common situations: A config file points at a directory instead of a file; a credentials path is a FIFO created by a wrapper script; a symlink to /dev/null or a procfs pseudo-file is configured; a provisioning tool created the wrong file type.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/3b47be8c5fa975c1. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/external_credentials.rs:224

        // call and flags require no variadic mode.
        let fd = unsafe { libc::openat(current.as_raw_fd(), component.as_ptr(), flags) };
        if fd < 0 {
            return Err(io::Error::last_os_error());
        }
        // SAFETY: `fd` is newly owned after the successful `openat`.
        current = unsafe { File::from_raw_fd(fd) };
        opened_leaf = leaf;
    }

    if !opened_leaf {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "external credential path must name a file",
        ));
    }
    let metadata = current.metadata()?;
    if !metadata.file_type().is_file() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "external credential path must name a regular file",
        ));
    }
    if require_owner_only {
        use std::os::unix::fs::MetadataExt as _;
        // SAFETY: geteuid(2) dereferences no pointers.
        if metadata.uid() != unsafe { libc::geteuid() }
            || metadata.mode() & 0o077 != 0
            || metadata.nlink() != 1
        {
            return Err(io::Error::new(
                io::ErrorKind::PermissionDenied,
                "Codewhale-owned credential file must be singly linked, owned by this user, and mode 0600 or stricter",
            ));
        }
    }
    Ok(current)

View on GitHub (pinned to 73e0f67d83)