{"record":{"id":"3542e71be4719ae9","repo":"Hmbown/CodeWhale","slug":"mcp-config-path-cannot-contain-components","errorCode":null,"errorMessage":"MCP config path cannot contain '..' components","messagePattern":"MCP config path cannot contain '\\.\\.' components","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp.rs","lineNumber":54,"sourceCode":"use self::stdio::{STDIO_SHUTDOWN_GRACE, StderrTail};\nuse self::wire::{is_mcp_stale_session_body, is_mcp_stale_session_error};\nuse crate::network_policy::{Decision, NetworkPolicyDecider, host_from_url};\nuse crate::utils::write_atomic;\n\n// === Error diagnostics helpers (#71) ===\n\n/// Bytes of a non-2xx response body to surface in connection errors.\nconst ERROR_BODY_PREVIEW_BYTES: usize = 200;\n\nfn validate_mcp_config_path(path: &Path) -> Result<()> {\n    if path.as_os_str().is_empty() {\n        anyhow::bail!(\"MCP config path cannot be empty\");\n    }\n    if path\n        .components()\n        .any(|component| matches!(component, Component::ParentDir))\n    {\n        anyhow::bail!(\"MCP config path cannot contain '..' components\");\n    }\n    Ok(())\n}\n\n/// Expand `${NAME}` placeholders in an MCP config value from the process\n/// environment. This lets secrets (API keys, bearer tokens, …) be supplied\n/// through environment variables instead of being written in cleartext into\n/// the MCP config file on disk.\n///\n/// On a missing or malformed placeholder the error names only the offending\n/// variable, never the surrounding value, so a secret-bearing string is never\n/// echoed into logs or error output.\nfn expand_env_placeholders_with(\n    value: &str,\n    environment: Option<&crate::plugins::HostEnvironment>,\n) -> Result<String> {\n    let mut out = String::new();\n    let mut rest = value;","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/mcp.rs#L36-L72","documentation":"The same validator rejects any config path containing a ParentDir ('..') component, regardless of whether it would still resolve somewhere legal. It is a deliberate security guard for user- and plugin-supplied MCP config paths: traversal is never interpreted, only refused.","triggerScenarios":"Config paths like ../shared/mcp.json, /etc/codewhale/../../home/user/mcp.json, or paths built by joining user input that carries '..' segments.","commonSituations":"Sharing configs across repos with ../../-style relative paths, dotfile or symlink layouts expressed with '..', automation tools emitting paths with redundant parent segments.","solutions":["Use an absolute path written out in full, with no '..' segments","Normalize the path first (canonicalize, or lexical normalization) so it contains no ParentDir components, then pass the normalized form","Restructure the setup so the MCP config lives at a fixed location referenced absolutely"],"exampleFix":"// before\nlet p = config_root.join(\"../../../shared/mcp.json\"); // contains ParentDir\n// after: normalize lexically to an absolute, '..'-free path\nlet p = lexically_normalize(config_root.join(\"../../../shared/mcp.json\"));","handlingStrategy":"validation","validationCode":"// Reject or normalize before handing the path to the MCP layer\nuse std::path::Component;\nfn safe_mcp_path(p: &std::path::Path) -> Option<std::path::PathBuf> {\n    if p.as_os_str().is_empty() {\n        return None;\n    }\n    let mut out = std::path::PathBuf::new();\n    for c in p.components() {\n        match c {\n            Component::ParentDir => return None, // or lexically pop()\n            other => out.push(other.as_os_str()),\n        }\n    }\n    Some(out)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Canonicalize user-supplied config paths at the input boundary","Store absolute paths in automation and dotfiles","Treat any '..' in a config path as a configuration bug, not a convenience"],"tags":["mcp","security","path-traversal","validation","config"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}