{"record":{"id":"d216f31a1d13e1c9","repo":"Hmbown/CodeWhale","slug":"project-workspace-path-cannot-contain-compone","errorCode":null,"errorMessage":"project workspace path cannot contain '..' components","messagePattern":"project workspace path cannot contain '\\.\\.' components","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/lib.rs","lineNumber":6660,"sourceCode":"            return Err(err).with_context(|| {\n                format!(\"failed to resolve config directory {}\", parent.display())\n            });\n        }\n    };\n    let normalized = parent.join(file_name);\n    reject_path_symlink(&normalized)?;\n    Ok(normalized)\n}\n\nfn normalize_project_workspace(workspace: &Path) -> Result<PathBuf> {\n    if workspace.as_os_str().is_empty() {\n        bail!(\"project workspace path cannot be empty\");\n    }\n    if workspace\n        .components()\n        .any(|component| matches!(component, Component::ParentDir))\n    {\n        bail!(\"project workspace path cannot contain '..' components\");\n    }\n    let absolute = if workspace.is_absolute() {\n        workspace.to_path_buf()\n    } else {\n        std::env::current_dir()\n            .context(\"failed to resolve current directory for project workspace\")?\n            .join(workspace)\n    };\n    match absolute.canonicalize() {\n        Ok(path) => Ok(path),\n        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {\n            Ok(normalize_path_components(&absolute))\n        }\n        Err(err) => Err(err).with_context(|| {\n            format!(\n                \"failed to resolve project workspace {}\",\n                workspace.display()\n            )","sourceCodeStart":6642,"sourceCodeEnd":6678,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/config/src/lib.rs#L6642-L6678","documentation":"Thrown by normalize_project_workspace when the workspace path contains a '..' (ParentDir) component. This is the same anti-traversal guard as the config path normalizer, applied to lane/project workspace roots; the input must be expressible without parent references before canonicalization is attempted.","triggerScenarios":"workspace = \"../repo\", \"../../team/worktree\", or any relative path walking upward from the current directory. The components check runs before the canonicalize/fallback step, so even a lexically-cancelling '..' is rejected.","commonSituations":"Monorepo scripts addressing sibling worktrees via ../; CI jobs checking out to nested dirs and passing ../../workspace; attempts to escape a sandbox root by '..'.","solutions":["Pass an absolute, '..'-free path to the workspace (pwd-realpath it first if needed)","Use a path relative to the current directory that descends only, e.g. worktrees/feature-x","Generate paths with realpath/CanonPath in your launcher so they never contain '..'"],"exampleFix":"# before\nworkspace = \"../repo\"\n\n# after\nworkspace = \"/home/user/repo\"","handlingStrategy":"validation","validationCode":"use std::path::{Component, Path};\n\nanyhow::ensure!(\n    !workspace.components().any(|c| matches!(c, Component::ParentDir)),\n    \"workspace path must not contain '..'\"\n);","typeGuard":"fn workspace_is_traversal_free(p: &std::path::Path) -> bool {\n    !p.components().any(|c| matches!(c, std::path::Component::ParentDir))\n}","tryCatchPattern":null,"preventionTips":["Pass absolute worktree paths you obtained from realpath/git worktree list","Reject '..' in user-supplied workspace paths before they reach the lane API"],"tags":["rust","lane","workspace","path-traversal","security"],"backgroundTag":"path-traversal-rejected","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}