{"record":{"id":"00fabcb95e11ebf4","repo":"Hmbown/CodeWhale","slug":"workspace-path-cannot-contain-components","errorCode":null,"errorMessage":"workspace path cannot contain '..' components","messagePattern":"workspace path cannot contain '\\.\\.' components","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp.rs","lineNumber":5619,"sourceCode":"fn workspace_allows_project_mcp_config(workspace: &Path) -> bool {\n    crate::config::is_workspace_trusted(workspace)\n}\n\nfn checked_workspace_mcp_config_path(workspace: &Path) -> Result<PathBuf> {\n    Ok(checked_workspace_path(workspace)?\n        .join(\".codewhale\")\n        .join(\"mcp.json\"))\n}\n\nfn checked_workspace_path(workspace: &Path) -> Result<PathBuf> {\n    if workspace.as_os_str().is_empty() {\n        anyhow::bail!(\"workspace path cannot be empty\");\n    }\n    if workspace\n        .components()\n        .any(|component| matches!(component, Component::ParentDir))\n    {\n        anyhow::bail!(\"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 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) => {\n            Err(err).with_context(|| format!(\"failed to resolve workspace {}\", workspace.display()))\n        }\n    }\n}","sourceCodeStart":5601,"sourceCodeEnd":5637,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp.rs#L5601-L5637","documentation":"`checked_workspace_path` rejects workspace paths containing `..` components to guarantee the derived `.codewhale/mcp.json` location stays unambiguous and cannot traverse outside the intended workspace. The library treats parent-dir components in a workspace root as invalid input rather than normalizing them away.","triggerScenarios":"Passing a workspace like `Path::new(\"../myproject\")` or `./a/../b` into any MCP config function that routes through `checked_workspace_path` (crates/tui/src/mcp.rs:5619).","commonSituations":"Users typing relative `../` paths on the command line; scripts building workspace paths by concatenation; config values copied with trailing relative segments.","solutions":["Canonicalize the workspace before passing it in: `workspace.canonicalize()?` removes `..` components","Convert the path to an absolute path via `std::env::current_dir()?.join(rel)` then `normalize_path_components`","Fix the stored configuration to use an absolute, `..`-free workspace path"],"exampleFix":"// before\nlet workspace = PathBuf::from(\"../other-project\");\n// after\nlet workspace = PathBuf::from(\"../other-project\").canonicalize()?; // absolute, no '..' components","handlingStrategy":"validation","validationCode":"use std::path::Component;\nif workspace.components().any(|c| matches!(c, Component::ParentDir)) {\n    let workspace = workspace.canonicalize()?;\n}","typeGuard":"fn has_parent_components(p: &std::path::Path) -> bool {\n    p.components().any(|c| matches!(c, Component::ParentDir))\n}","tryCatchPattern":"let workspace = workspace.canonicalize().map_err(|e| anyhow::anyhow!(\"cannot normalize workspace {workspace:?}: {e}\"))?;","preventionTips":["Canonicalize user-supplied paths before storing them in config","Use absolute paths for workspace settings","Reject or normalize '..' at the config-parsing layer"],"tags":["mcp","validation","path","workspace"],"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-23T08:17:48.524Z"}