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
- chmod 600 <file> to strip group/other bits
- chown the file back to the running user: sudo chown "$USER:" <file>
- Drop extra hard links: locate them with find / -samefile <file> 2>/dev/null, then copy-and-replace the file so nlink returns to 1
- 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
- Never edit Codewhale-owned credential files with sudo; re-login instead
- After restores/copies, verify mode 0600, current-user ownership, and nlink == 1
- Avoid backup tools that hard-link dedupe credential files
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
- Codewhale credentials directory must be owned by the current
- xAI OAuth path must be a regular file
- xAI OAuth file must be owned by the current user
- xAI OAuth file must not have multiple filesystem links
- external credential path escapes its absolute root: {}
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/3b8bfdff3ea9f74f.
Report an issue: GitHub.