{"record":{"id":"01e23f92db38d5c4","repo":"BloopAI/vibe-kanban","slug":"file-source-file-is-outside-project-directory","errorCode":null,"errorMessage":"File {source_file:?} is outside project directory","messagePattern":"File (.+?) is outside project directory","errorType":"validation","errorClass":"ContainerError","httpStatus":null,"severity":"error","filePath":"crates/local-deployment/src/copy.rs","lineNumber":86,"sourceCode":"                tracing::warn!(\"Failed to copy file {:?}: {e}\", entry.path());\n            }\n        }\n    }\n\n    Ok(())\n}\n\nfn copy_single_file(\n    source_file: &Path,\n    source_root: &Path,\n    target_root: &Path,\n    seen: &mut HashSet<PathBuf>,\n) -> Result<bool, ContainerError> {\n    let canonical_source = source_root.canonicalize()?;\n    let canonical_file = source_file.canonicalize()?;\n    // Validate path is within source_dir\n    if !canonical_file.starts_with(canonical_source) {\n        return Err(ContainerError::Other(anyhow!(\n            \"File {source_file:?} is outside project directory\"\n        )));\n    }\n\n    if !seen.insert(canonical_file.clone()) {\n        return Ok(false);\n    }\n\n    let relative_path = source_file.strip_prefix(source_root).map_err(|e| {\n        ContainerError::Other(anyhow!(\n            \"Failed to get relative path for {source_file:?}: {e}\"\n        ))\n    })?;\n\n    let target_file = target_root.join(relative_path);\n\n    if target_file.exists() {\n        return Ok(false);","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/local-deployment/src/copy.rs#L68-L104","documentation":"copy_single_file canonicalizes both the source file and the source root and rejects any file whose canonical path is not inside the source root. This path-traversal guard prevents copying files outside the project directory via symlinks or '../' style paths. It protects the target from unintended files.","triggerScenarios":"copy_files contains an entry that resolves (after canonicalize) outside source_root: absolute paths elsewhere on disk, symlinked files pointing out of the project, or '../' relative entries.","commonSituations":"copy_files lists a symlink (e.g. node binaries, env files) pointing to /usr/... or $HOME; misconfigured copy list with typos like '../shared/config.json'; projects using symlinked asset directories outside the repo.","solutions":["Remove out-of-project entries from the copy_files list; copy them explicitly to the target instead.","Replace symlinks with real files inside the project, or copy the symlink target contents into the project first.","Verify paths resolve within the project: run `realpath <file>` and compare against the project root before copying.","If the file is legitimately needed, mount/copy it into the project directory beforehand."],"exampleFix":"// before\ncopy_files: vec![\"/usr/local/bin/node\".into()]\n// after\ncopy_files: vec![\"./vendor/bin/node\".into()] // real file inside project","handlingStrategy":"validation","validationCode":"let canonical_root = source_root.canonicalize()?;\nfor f in &copy_files {\n    let p = source_root.join(f).canonicalize()?;\n    if !p.starts_with(&canonical_root) {\n        return Err(anyhow!(\"copy_files entry {:?} escapes project root\", f));\n    }\n}","typeGuard":"fn is_inside(root: &Path, file: &Path) -> bool {\n    file.canonicalize().ok()\n        .zip(root.canonicalize().ok())\n        .map(|(f, r)| f.starts_with(r))\n        .unwrap_or(false)\n}","tryCatchPattern":"if let Err(ContainerError::Other(e)) = copy_project_files(...).await {\n    let msg = e.to_string();\n    if msg.contains(\"outside project directory\") {\n        tracing::error!(%msg, \"remove or inline the offending copy_files entry\");\n    }\n    return Err(e);\n}","preventionTips":["Never list symlinks pointing outside the repo in copy_files.","Run `realpath` checks on copy list entries in CI.","Resolve shared assets into the project tree before copying.","Avoid '..' or absolute paths in copy_files entries."],"tags":["path-traversal","filesystem","validation","security"],"backgroundTag":"path-outside-project-root","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}