Hmbown/CodeWhale · error
xAI OAuth file must not have multiple filesystem links
Error message
xAI OAuth file must not have multiple filesystem links
What it means
This library hardens the xAI OAuth credential file against link-based attacks: it opens the file, then verifies the underlying metadata (regular file, owned by the current euid, hardlink count exactly 1). If `st_nlink` is greater than 1, someone could hardlink the credential file to another location and read or tamper with tokens outside the secured directory, so the open is rejected.
Solutions
- Delete the file and log in again so the store recreates a fresh, single-link token file
- Find and remove extra hard links: `find / -samefile <token-path>` then delete the stray link, keeping only the one in the credentials directory
- Replace the hardlinked file with a real copy: `cp --remove-destination <file> <file>` (or `cp -L` then remove the link source)
- Check sync/backup software configuration to copy rather than hardlink credential files
Example fix
// before (fails: file has 2 links) ln ~/.codewhale/xai/auth.json ~/backup/auth.json // after cp ~/.codewhale/xai/auth.json ~/backup/auth.json
Defensive patterns
Strategy: validation
Validate before calling
use std::os::unix::fs::MetadataExt;
fn is_safe_credential_file(p: &std::path::Path) -> std::io::Result<bool> {
let m = std::fs::metadata(p)?;
Ok(m.is_file() && m.uid() == unsafe { libc::geteuid() } && m.nlink() == 1)
} Type guard
fn hardlink_count(p: &std::path::Path) -> std::io::Result<u64> {
Ok(std::fs::metadata(p)?.nlink())
} Try / catch
match store.load_tokens() {
Ok(t) => t,
Err(e) if e.to_string().contains("multiple filesystem links") => {
eprintln!("credential file hardlinked; removing and re-authenticating");
let _ = store.logout();
store.login()? // recreates a clean file
}
Err(e) => return Err(e),
} Prevention
- Never hardlink token files; always copy with `cp` (not `cp -l`)
- Configure backup/sync tools (rsync, restic, dedup) to copy credential files
- Restore credentials into place with copy, not link-based snapshots
- Keep all Codewhale instances pointed at the same owned directory
When it happens
Trigger: Any read/write/remove/rename/open path through validate_owned_file_handle encounters a credentials file whose link count is not 1 — typically because the user (or a backup/restore/sync tool) created a hard link to the token file.
Common situations: Restoring credentials from a backup tool that hardlinks instead of copying; syncing the ~/.codewhale directory with a tool like rsync --link-dest; a developer manually running `ln` on the token file to share it between machines or profiles.
Related errors
- Codewhale-owned credential file must be singly linked
- Codewhale-owned credential file must be singly linked…
- external credential path contains a NUL byte
- external credential path must be absolute
- external credential path must be lexically normalized
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/7bb3a2f47b07794c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/config/src/xai_credentials.rs:908
}
}
#[cfg(unix)]
fn validate_owned_file_handle(file: &File, path: &Path) -> Result<fs::Metadata> {
use std::os::unix::fs::MetadataExt as _;
let metadata = file.metadata().with_context(|| {
format!(
"inspecting Codewhale-owned xAI OAuth file {}",
crate::quote_os_path(path)
)
})?;
anyhow::ensure!(metadata.is_file(), "xAI OAuth path must be a regular file");
// SAFETY: geteuid(2) dereferences no pointers.
anyhow::ensure!(
metadata.uid() == unsafe { libc::geteuid() },
"xAI OAuth file must be owned by the current user"
);
anyhow::ensure!(
metadata.nlink() == 1,
"xAI OAuth file must not have multiple filesystem links"
);
Ok(metadata)
}
#[cfg(windows)]
fn open_owned_credentials_directory(directory: &Path) -> Result<XaiOAuthCredentialStore> {
use std::os::windows::fs::OpenOptionsExt as _;
use windows_sys::Win32::Storage::FileSystem::{
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_READ,
FILE_SHARE_READ, FILE_SHARE_WRITE, WRITE_DAC, WRITE_OWNER,
};
anyhow::ensure!(
directory.is_absolute(),
"xAI OAuth credentials directory must be absolute"
);View on GitHub (pinned to 73e0f67d83)