{"record":{"id":"e88d24a1feb5495a","repo":"Hmbown/CodeWhale","slug":"xai-oauth-path-must-be-a-regular-file","errorCode":null,"errorMessage":"xAI OAuth path must be a regular file","messagePattern":"xAI OAuth path must be a regular file","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/xai_credentials.rs","lineNumber":768,"sourceCode":"            )\n        } != 0\n        {\n            return Err(std::io::Error::last_os_error()).context(\"retiring xAI OAuth file\");\n        }\n        Ok(())\n    }\n}\n\n#[cfg(unix)]\nfn validate_owned_file_handle(file: &File, path: &Path) -> Result<fs::Metadata> {\n    use std::os::unix::fs::MetadataExt as _;\n    let metadata = file.metadata().with_context(|| {\n        format!(\n            \"inspecting Codewhale-owned xAI OAuth file {}\",\n            crate::quote_os_path(path)\n        )\n    })?;\n    anyhow::ensure!(metadata.is_file(), \"xAI OAuth path must be a regular file\");\n    anyhow::ensure!(\n        metadata.uid() == unsafe { libc::geteuid() },\n        \"xAI OAuth file must be owned by the current user\"\n    );\n    anyhow::ensure!(\n        metadata.nlink() == 1,\n        \"xAI OAuth file must not have multiple filesystem links\"\n    );\n    Ok(metadata)\n}\n\n#[cfg(windows)]\nfn open_owned_credentials_directory(directory: &Path) -> Result<XaiOAuthCredentialStore> {\n    use std::os::windows::fs::OpenOptionsExt as _;\n    use windows_sys::Win32::Storage::FileSystem::{\n        FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_READ,\n        FILE_SHARE_READ, FILE_SHARE_WRITE, WRITE_DAC, WRITE_OWNER,\n    };","sourceCodeStart":750,"sourceCodeEnd":786,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/config/src/xai_credentials.rs#L750-L786","documentation":"validate_owned_file_handle runs on every opened credential file and on the lifecycle lock: after an O_NOFOLLOW open it verifies the inode is a regular file. Symlinks usually fail earlier with ELOOP at open time; this check catches FIFOs, sockets, devices, or a non-regular file substituted between open and metadata(), so tokens are never read from or written to special files.","triggerScenarios":"xai-auth.json, a generation file, or .xai-oauth.lock inside $CODEWHALE_HOME/credentials is a FIFO, unix socket, or device file; or the path is swapped for a non-regular file between the openat and the metadata call.","commonSituations":"Misconfigured dotfile tooling or pranks creating special files in the credentials directory; symlink-attack leftovers (these usually surface as the ELOOP open error instead); stale test fixtures.","solutions":["Inspect the entry: ls -l \"$CODEWHALE_HOME/credentials\" and look for type markers p, s, c, or b","Remove the offending file: rm \"$CODEWHALE_HOME/credentials/<name>\", then re-run codewhale auth xai-device","Find and stop whatever creates non-regular files there"],"exampleFix":"# before\nls -l \"$HOME/.codewhale/credentials\"\n# prw-r--r-- 1 me me 0 xai-auth.json   (FIFO)\n\n# after\nrm \"$HOME/.codewhale/credentials/xai-auth.json\"\ncodewhale auth xai-device","handlingStrategy":"validation","validationCode":"#[cfg(unix)]\nfn is_plain_file(p: &std::path::Path) -> std::io::Result<bool> {\n    let ft = std::fs::symlink_metadata(p)?.file_type();\n    Ok(!ft.is_symlink() && ft.is_file())\n}","typeGuard":"#[cfg(unix)]\nfn credential_file_is_regular(dir: &std::path::Path, name: &str) -> bool {\n    std::fs::symlink_metadata(dir.join(name))\n        .map(|m| !m.file_type().is_symlink() && m.is_file())\n        .unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Never symlink credential files; move them instead","Keep the credentials directory out of dotfile managers","Audit the directory for non-regular entries when logins fail unexpectedly"],"tags":["unix","security","filesystem","xai-oauth"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}