Hmbown/CodeWhale · error · std::io::Error

PermissionDenied

PermissionDenied

Error message

Codewhale-owned credential file must be singly linked, owned by this user, and mode 0600 or stricter

What it means

read_codewhale_owned_to_string opens Codewhale-owned credential files with require_owner_only=true: the opened handle's fstat must show uid equal to the effective uid, mode & 0o077 == 0 (no group/other permission bits), and nlink == 1 (no extra hard links). Violating any one of the three fails closed with PermissionDenied. Grants for third-party-owned (external) files skip this strictness, so it applies specifically to files Codewhale itself created.

Source

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

        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 _;
        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)
}

#[cfg(windows)]
fn open_secure_regular_file(path: &Path, require_owner_only: bool) -> io::Result<File> {
    use std::ffi::OsString;
    use std::os::windows::ffi::OsStringExt;
    use std::os::windows::fs::{MetadataExt, OpenOptionsExt};
    use std::os::windows::io::AsRawHandle;
    use std::path::Component;
    use windows_sys::Win32::Storage::FileSystem::{
        FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_OPEN_REPARSE_POINT, FILE_NAME_OPENED,
        GetFinalPathNameByHandleW, VOLUME_NAME_DOS,

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. chmod 600 <file> to strip group/other bits
  2. chown the file back to the running user: sudo chown "$USER:" <file>
  3. Drop extra hard links: locate them with find / -samefile <file> 2>/dev/null, then copy-and-replace the file so nlink returns to 1
  4. Re-create the credential through codewhale itself (re-login/re-add key) if fixing metadata in place is awkward

Example fix

# before
ls -l ~/.codewhale/credentials/token   # -rw-r--r-- 2 user user ...

# after
chmod 600 ~/.codewhale/credentials/token
sudo chown "$USER:" ~/.codewhale/credentials/token
# remove the extra hard link found via: find ~ -samefile ~/.codewhale/credentials/token
Defensive patterns

Strategy: validation

Validate before calling

use std::os::unix::fs::MetadataExt;

let meta = std::fs::metadata(path)?;
if meta.uid() != unsafe { libc::geteuid() } || meta.mode() & 0o077 != 0 || meta.nlink() != 1 {
    return Err("credential file must be owner-only, singly linked; run chmod 600 and fix ownership".into());
}

Type guard

fn credential_perms_safe(meta: &std::fs::Metadata) -> bool {
    use std::os::unix::fs::MetadataExt;
    meta.mode() & 0o077 == 0 && meta.nlink() == 1
}

Try / catch

match read_codewhale_owned_to_string(path) {
    Err(e) if e.downcast_ref::<io::Error>().is_some_and(|io| io.kind() == io::ErrorKind::PermissionDenied) => {
        // fail-closed security check: instruct chmod 600 / chown / remove extra links;
        // never auto-relax permissions on behalf of the user
    }
    other => other?,
}

Prevention

When it happens

Trigger: chmod 644/666 on the credential file; ownership changed to root after editing with sudo or restoring as root; nlink > 1 because a backup tool created hard links; any restore/copy pipeline that relaxed the mode or added links.

Common situations: Editing credentials with sudo; cp/rsync without -p or with a permissive umask; backup tools that hard-link dedupe; provisioning scripts that chmod 644 everything for 'readability'.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/3b8bfdff3ea9f74f. Report an issue: GitHub.