{"record":{"id":"1e690c464e8304dc","repo":"Hmbown/CodeWhale","slug":"invalidinput-1e690c","errorCode":"InvalidInput","errorMessage":"external credential path must be absolute","messagePattern":"external credential path must be absolute","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/external_credentials.rs","lineNumber":160,"sourceCode":"        );\n    }\n    String::from_utf8(bytes).map(Some).with_context(|| {\n        format!(\n            \"Codewhale-owned credential file {} is not valid UTF-8\",\n            codewhale_config::quote_os_path(path)\n        )\n    })\n}\n\n#[cfg(unix)]\nfn open_secure_regular_file(path: &Path, require_owner_only: bool) -> io::Result<File> {\n    use std::ffi::CString;\n    use std::os::fd::FromRawFd;\n    use std::os::unix::ffi::OsStrExt;\n    use std::path::Component;\n\n    if !path.is_absolute() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidInput,\n            \"external credential path must be absolute\",\n        ));\n    }\n\n    let root = CString::new(\"/\").expect(\"static root contains no NUL\");\n    // SAFETY: `root` is a valid C string and flags require no variadic mode.\n    let root_fd = unsafe {\n        libc::open(\n            root.as_ptr(),\n            libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC,\n        )\n    };\n    if root_fd < 0 {\n        return Err(io::Error::last_os_error());\n    }\n    // SAFETY: `root_fd` is newly owned after the successful `open`.\n    let mut current = unsafe { File::from_raw_fd(root_fd) };","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/external_credentials.rs#L142-L178","documentation":"open_secure_regular_file (Unix) requires external credential paths to be absolute before it walks from / with openat, refusing anything relative as the first gate of the no-follow traversal that opens the exact granted file. Relative paths fail immediately with InvalidInput — no cwd-based resolution is attempted, which is deliberate: a consented credential path must be unambiguous.","triggerScenarios":"An external-credential grant/config holding a relative value such as \"./token\" or \"keys/api.json\", or a tilde path like \"~/.config/provider/token\" — the OS does not expand ~ inside environment variables, so it stays relative and is rejected here.","commonSituations":"Env-based config written in .env files or scripts where the author assumed ~ expansion; running from a different working directory than when the relative path happened to work; values copy-pasted from docs showing shell-style paths.","solutions":["Use a fully expanded absolute path: export PROVIDER_CREDENTIALS=/home/user/.config/provider/token.json","Expand ~ at assignment time in the shell: export PROVIDER_CREDENTIALS=\"$HOME/.config/provider/token.json\"","In code, absolutize against a config root or canonicalize before passing the path into the credential reader"],"exampleFix":"# before\nexport PROVIDER_CREDENTIALS=\"~/.config/provider/token.json\"   # stays literal, relative\n\n# after\nexport PROVIDER_CREDENTIALS=\"$HOME/.config/provider/token.json\"   # expanded, absolute","handlingStrategy":"validation","validationCode":"let path = Path::new(&value);\nif !path.is_absolute() {\n    return Err(format!(\"credential path must be absolute, got: {value:?}\"));\n}","typeGuard":"fn is_absolute_credential_path(p: &Path) -> bool {\n    p.is_absolute()\n}","tryCatchPattern":null,"preventionTips":["Expand ~ at assignment time ($HOME/...) — env vars never expand it for you","Validate env/config path values with is_absolute() at load time and name the variable in the error","Prefer absolutizing against a known config root in code instead of trusting user input"],"tags":["credentials","path-validation","security","configuration"],"backgroundTag":"path-validation-failed","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}