{"record":{"id":"fb016bebb2757a76","repo":"Hmbown/CodeWhale","slug":"fleet-artifact-path-must-stay-within-the-workspace","errorCode":null,"errorMessage":"Fleet artifact path must stay within the workspace","messagePattern":"Fleet artifact path must stay within the workspace","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/fleet/files.rs","lineNumber":16,"sourceCode":"//! Workspace-confined file operations shared by Fleet artifacts and its ledger.\n\nuse std::fs::File;\nuse std::io::{self, Write};\nuse std::path::{Component, Path};\n\npub(crate) fn path_is_confined(path: &Path) -> bool {\n    !path.as_os_str().is_empty()\n        && path.components().all(|component| match component {\n            Component::Normal(name) => !cfg!(windows) || !name.as_encoded_bytes().contains(&b':'),\n            _ => false,\n        })\n}\n\nfn invalid_path() -> io::Error {\n    io::Error::new(\n        io::ErrorKind::InvalidInput,\n        \"Fleet artifact path must stay within the workspace\",\n    )\n}\n\n#[cfg(unix)]\n#[derive(Debug)]\npub(crate) struct WorkspaceFile {\n    directory: File,\n    filename: std::ffi::CString,\n}\n\n#[cfg(unix)]\nimpl WorkspaceFile {\n    pub(crate) fn open(workspace: &Path, relative: &Path, create: bool) -> io::Result<Self> {\n        use std::os::fd::{AsRawFd, FromRawFd};\n        use std::os::unix::ffi::OsStrExt;\n        if !path_is_confined(relative) {","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/fleet/files.rs#L1-L34","documentation":"This error means the resolved Fleet artifact path escaped the workspace directory or contained illegal components (e.g. a drive colon on Windows, non-Normal components). The library throws it from invalid_path, used by WorkspaceFile::open and sibling, whenever validation of the joined path fails.","triggerScenarios":"Passing a filename containing '..' or absolute components, or on Windows a name containing a ':' (alternate data stream / drive letter), to Fleet artifact open/sibling APIs.","commonSituations":"Building artifact names from untrusted or user-supplied input containing path separators or '..'; Windows filenames with a colon intended as a timestamp; misconfigured artifact directory.","solutions":["Sanitize the artifact name: strip path separators and reject '..' before joining","On Windows, remove or replace any ':' in the filename","Join relative names onto the workspace directory and verify the result stays within it before calling open"],"exampleFix":"// before\nlet name = format!(\"{}.json\", user_input);\n// after\nlet name = format!(\"{}.json\", user_input.replace(['/', '\\\\', ':'], \"_\"));","handlingStrategy":"validation","validationCode":"fn safe_artifact_name(name: &str) -> Option<&str> {\n    let ok = !name.is_empty()\n        && !name.contains('..')\n        && !name.contains('/')\n        && !name.contains('\\\\')\n        && !(cfg!(windows) && name.contains(':'));\n    ok.then_some(name)\n}","typeGuard":null,"tryCatchPattern":"match WorkspaceFile::open(dir, name, write) {\n    Ok(f) => use_file(f),\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => {\n        eprintln!(\"artifact name escaped workspace or contains illegal characters\");\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Treat artifact names as untrusted: strip separators, '..' and ':' before joining","Always join names onto a fixed workspace dir and canonicalize to verify containment","Never build Fleet paths from raw user input"],"tags":["path","validation","fleet","path-traversal"],"backgroundTag":"path-traversal-blocked","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}