{"record":{"id":"f2a01228220a6742","repo":"Hmbown/CodeWhale","slug":"project-mcp-server-cwd-must-stay-within-workspace","errorCode":null,"errorMessage":"Project MCP server cwd must stay within workspace: {}","messagePattern":"Project MCP server cwd must stay within workspace: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp.rs","lineNumber":3883,"sourceCode":"    } else {\n        std::env::current_dir()\n            .unwrap_or_else(|_| PathBuf::from(\".\"))\n            .join(workspace)\n    };\n    normalize_path_components(&absolute)\n}\n\nfn resolve_project_mcp_cwd(workspace: &Path, cwd: Option<&Path>) -> Result<PathBuf> {\n    let cwd = match cwd {\n        Some(cwd) if cwd.is_relative() => normalize_path_components(&workspace.join(cwd)),\n        Some(cwd) => normalize_path_components(cwd),\n        None => workspace.to_path_buf(),\n    };\n    let resolved = cwd\n        .canonicalize()\n        .unwrap_or_else(|_| normalize_path_components(&cwd));\n    if !resolved.starts_with(workspace) {\n        anyhow::bail!(\n            \"Project MCP server cwd must stay within workspace: {}\",\n            resolved.display()\n        );\n    }\n    Ok(resolved)\n}\n\nfn normalize_path_components(path: &Path) -> PathBuf {\n    let mut normalized = PathBuf::new();\n    for component in path.components() {\n        match component {\n            Component::Prefix(_) | Component::RootDir => {\n                normalized.push(component.as_os_str());\n            }\n            Component::CurDir => {}\n            Component::ParentDir => {\n                normalized.pop();\n            }","sourceCodeStart":3865,"sourceCodeEnd":3901,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/mcp.rs#L3865-L3901","documentation":"Servers declared in a workspace's .codewhale/mcp.json get their cwd resolved like plugin cwds: relative cwds are joined to the workspace, the result is canonicalized, and it must remain inside the (canonicalized) workspace root. A cwd that escapes via absolute path, '..', or a symlink is rejected so a project config cannot launch processes outside the project.","triggerScenarios":"A project .codewhale/mcp.json server entry with cwd set to an absolute path outside the workspace, a relative path containing '..', or a directory that is a symlink pointing outside the workspace.","commonSituations":"A repo whose MCP server expects to run from a monorepo sibling directory; the workspace path itself containing a symlink so canonicalization changes the prefix; moving the repo so a previously-inner path resolves elsewhere.","solutions":["Keep the server process cwd inside the workspace ('.' or a subdirectory)","If the server must run elsewhere, declare it in the user-level ~/.codewhale/mcp.json instead of the project config","Remove symlink hops inside the workspace so canonicalized paths stay under the workspace root"],"exampleFix":"// before (.codewhale/mcp.json)\n\"build-server\": { \"command\": \"./watch.sh\", \"cwd\": \"../shared-scripts\" }\n\n// after\n\"build-server\": { \"command\": \"./watch.sh\", \"cwd\": \".\" }","handlingStrategy":"validation","validationCode":"// Before starting a project MCP server, verify its cwd stays in the workspace:\nlet resolved = declared_cwd.canonicalize().unwrap_or_else(|_| normalize_path_components(&declared_cwd));\nanyhow::ensure!(resolved.starts_with(&canonical_workspace), \"project MCP cwd escapes workspace\");","typeGuard":"fn project_cwd_within_workspace(cwd: &std::path::Path, workspace: &std::path::Path) -> bool {\n    cwd.canonicalize()\n        .map(|c| c.starts_with(workspace))\n        .unwrap_or_else(|_| normalize_path_components(cwd).starts_with(workspace))\n}","tryCatchPattern":null,"preventionTips":["Write project .codewhale/mcp.json with cwd '.' or in-repo subdirectories only","Put servers that must run outside the workspace into the user-level config instead","Avoid symlinks inside the workspace that resolve outside it - canonicalization will trip the guard"],"tags":["mcp","workspace","security","cwd"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}