{"record":{"id":"3b8bfdff3ea9f74f","repo":"Hmbown/CodeWhale","slug":"permissiondenied","errorCode":"PermissionDenied","errorMessage":"Codewhale-owned credential file must be singly linked, owned by this user, and mode 0600 or stricter","messagePattern":"Codewhale-owned credential file must be singly linked, owned by this user, and mode 0600 or stricter","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/external_credentials.rs","lineNumber":247,"sourceCode":"        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            \"external credential path must name a file\",\n        ));\n    }\n    let metadata = current.metadata()?;\n    if !metadata.file_type().is_file() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            \"external credential path must name a regular file\",\n        ));\n    }\n    if require_owner_only {\n        use std::os::unix::fs::MetadataExt as _;\n        if metadata.uid() != unsafe { libc::geteuid() }\n            || metadata.mode() & 0o077 != 0\n            || metadata.nlink() != 1\n        {\n            return Err(io::Error::new(\n                io::ErrorKind::PermissionDenied,\n                \"Codewhale-owned credential file must be singly linked, owned by this user, and mode 0600 or stricter\",\n            ));\n        }\n    }\n    Ok(current)\n}\n\n#[cfg(windows)]\nfn open_secure_regular_file(path: &Path, require_owner_only: bool) -> io::Result<File> {\n    use std::ffi::OsString;\n    use std::os::windows::ffi::OsStringExt;\n    use std::os::windows::fs::{MetadataExt, OpenOptionsExt};\n    use std::os::windows::io::AsRawHandle;\n    use std::path::Component;\n    use windows_sys::Win32::Storage::FileSystem::{\n        FILE_ATTRIBUTE_REPARSE_POINT, FILE_FLAG_OPEN_REPARSE_POINT, FILE_NAME_OPENED,\n        GetFinalPathNameByHandleW, VOLUME_NAME_DOS,","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/external_credentials.rs#L229-L265","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","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"],"exampleFix":"# before\nls -l ~/.codewhale/credentials/token   # -rw-r--r-- 2 user user ...\n\n# after\nchmod 600 ~/.codewhale/credentials/token\nsudo chown \"$USER:\" ~/.codewhale/credentials/token\n# remove the extra hard link found via: find ~ -samefile ~/.codewhale/credentials/token","handlingStrategy":"validation","validationCode":"use std::os::unix::fs::MetadataExt;\n\nlet meta = std::fs::metadata(path)?;\nif meta.uid() != unsafe { libc::geteuid() } || meta.mode() & 0o077 != 0 || meta.nlink() != 1 {\n    return Err(\"credential file must be owner-only, singly linked; run chmod 600 and fix ownership\".into());\n}","typeGuard":"fn credential_perms_safe(meta: &std::fs::Metadata) -> bool {\n    use std::os::unix::fs::MetadataExt;\n    meta.mode() & 0o077 == 0 && meta.nlink() == 1\n}","tryCatchPattern":"match read_codewhale_owned_to_string(path) {\n    Err(e) if e.downcast_ref::<io::Error>().is_some_and(|io| io.kind() == io::ErrorKind::PermissionDenied) => {\n        // fail-closed security check: instruct chmod 600 / chown / remove extra links;\n        // never auto-relax permissions on behalf of the user\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["credentials","permissions","unix","security","file-mode"],"backgroundTag":"insecure-file-permissions","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}