{"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":64,"sourceCode":"const ERROR_BODY_PREVIEW_BYTES: usize = 200;\n\n/// Newest dated MCP protocol revision Codewhale advertises at `initialize` and\n/// answers as an MCP server. Matches the shared MCP crate (`crates/mcp`).\npub(crate) const MCP_PROTOCOL_VERSION: &str = \"2025-06-18\";\n/// Dated MCP revisions accepted during negotiation, newest first. A peer\n/// answering or requesting any of these continues the handshake.\npub(crate) const MCP_SUPPORTED_PROTOCOL_VERSIONS: &[&str] =\n    &[MCP_PROTOCOL_VERSION, \"2025-03-26\", \"2024-11-05\"];\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":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp.rs#L46-L82","documentation":"MCP config paths must not contain `..` parent-directory components; validate_mcp_config_path rejects them to prevent escaping the intended config location (path-traversal guard). This check runs on every config load, read, mutation, and workspace source switch.","triggerScenarios":"Passing a path like `~/projects/../mcp.json` or `configs/../../etc/mcp.json` via --mcp-config or a config field; programmatically building an MCP config path with relative parent hops.","commonSituations":"Users trying to keep an MCP config outside the expected directory via relative paths; scripts joining paths with `..` instead of normalizing; symlink-adjacent traversal attempts blocked defensively.","solutions":["Provide a normalized absolute path with no `..` components (e.g. via `realpath` or `canonicalize`)","Move or point the config to a path inside the intended directory instead of traversing upward","In scripts, use `$(realpath path)` before passing to --mcp-config"],"exampleFix":"# before\ncodewhale --mcp-config ~/projects/../codewhale/mcp.json ...\n# after\ncodewhale --mcp-config \"$(realpath ~/projects/../codewhale/mcp.json)\" ...","handlingStrategy":"validation","validationCode":"const p = require(\"path\").resolve(mcpConfigPath);\nif (p.split(require(\"path\").sep).includes(\"..\")) {\n  throw new Error(\"MCP config path must not contain '..' components\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  loadMcpConfig(userPath);\n} catch (e) {\n  if (String(e).includes(\"'..' components\")) {\n    // suggest canonicalizing: path.resolve(userPath), then retry\n  }\n}","preventionTips":["Always canonicalize user-supplied paths (realpath/resolve) before storing them in config","Keep MCP configs inside a single known directory","Never construct config paths via manual string concatenation with `..`"],"tags":["mcp","path","security"],"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"}