{"record":{"id":"250df79abb865d7b","repo":"astrid-runtime/astrid","slug":"mountpoint-contains-traversal","errorCode":null,"errorMessage":"mountpoint contains traversal: {}","messagePattern":"mountpoint contains traversal: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-storage-provider-fskit/src/main.rs","lineNumber":535,"sourceCode":"}\n\n#[cfg(not(unix))]\nfn validate_mountpoint_ancestors(mountpoint: &Path) -> Result<()> {\n    let _ = mountpoint;\n    Ok(())\n}\n\nfn validate_mountpoint_layout(mountpoint: &Path) -> Result<()> {\n    if !mountpoint.is_absolute() {\n        bail!(\"mountpoint must be absolute\");\n    }\n    if mountpoint.components().any(|component| {\n        matches!(\n            component,\n            std::path::Component::ParentDir | std::path::Component::CurDir\n        )\n    }) {\n        bail!(\"mountpoint contains traversal: {}\", mountpoint.display());\n    }\n    if mountpoint.parent().is_none() {\n        bail!(\"mountpoint must be below a parent directory\");\n    }\n    Ok(())\n}\n\n#[cfg(target_os = \"macos\")]\npub(crate) async fn native_mount(lease: &StorageMountLeaseV1, mountpoint: &Path) -> Result<()> {\n    let output = tokio::process::Command::new(\"/sbin/mount\")\n        .arg(\"-t\")\n        .arg(\"astridfs\")\n        .arg(&lease.resource_path)\n        .arg(mountpoint)\n        .output()\n        .await\n        .context(\"invoke macOS FSKit mount\")?;\n    if !output.status.success() {","sourceCodeStart":517,"sourceCodeEnd":553,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage-provider-fskit/src/main.rs#L517-L553","documentation":"validate_mountpoint_layout detects ParentDir (..) or CurDir (.) components in the mountpoint and bails with the full offending path. Traversal components make the effective mount target depend on the process working directory and can be abused to redirect the mount outside the intended directory, so they are rejected outright.","triggerScenarios":"Passing paths like /Volumes/../etc/mnt or ./mnt/fs to mount/unmount or any of the validate_* entry points.","commonSituations":"Concatenating strings instead of using PathBuf; user-supplied mount names containing \"..\"; normalizing with naive string joins rather than Path APIs.","solutions":["Remove \"..\" and \".\" components; express the mountpoint as a clean absolute path","Canonicalize the path before passing it: std::fs::canonicalize (existing path) or lexical normalization","Validate/reject the user input upstream before constructing the mountpoint"],"exampleFix":"// before\nlet mountpoint = PathBuf::from(\"/Volumes/./fskit/../fskit-mnt\");\n// after\nlet mountpoint = PathBuf::from(\"/Volumes/fskit-mnt\");","handlingStrategy":"validation","validationCode":"fn ensure_no_traversal(mp: &std::path::Path) -> anyhow::Result<()> {\n    let bad = mp.components().any(|c| matches!(c, std::path::Component::ParentDir | std::path::Component::CurDir));\n    anyhow::ensure!(!bad, \"mountpoint contains traversal: {}\", mp.display());\n    Ok(())\n}","typeGuard":"fn is_clean_path(p: &std::path::Path) -> bool {\n    !p.components().any(|c| matches!(c, std::path::Component::ParentDir | std::path::Component::CurDir))\n}","tryCatchPattern":null,"preventionTips":["Normalize paths with canonicalize or lexical cleanup before use","Sanitize user-supplied path components (reject \"..\" and \".\")","Use Path APIs, never string concatenation, for paths"],"tags":["security","path-traversal","filesystem","mount"],"backgroundTag":"path-traversal-blocked","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}