Hmbown/CodeWhale · error

MCP config path cannot contain '..' components

Error message

MCP config path cannot contain '..' components

What it means

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.

Solutions

  1. Provide a normalized absolute path with no `..` components (e.g. via `realpath` or `canonicalize`)
  2. Move or point the config to a path inside the intended directory instead of traversing upward
  3. In scripts, use `$(realpath path)` before passing to --mcp-config

Example fix

# before
codewhale --mcp-config ~/projects/../codewhale/mcp.json ...
# after
codewhale --mcp-config "$(realpath ~/projects/../codewhale/mcp.json)" ...
Defensive patterns

Strategy: validation

Validate before calling

const p = require("path").resolve(mcpConfigPath);
if (p.split(require("path").sep).includes("..")) {
  throw new Error("MCP config path must not contain '..' components");
}

Try / catch

try {
  loadMcpConfig(userPath);
} catch (e) {
  if (String(e).includes("'..' components")) {
    // suggest canonicalizing: path.resolve(userPath), then retry
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/3542e71be4719ae9. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp.rs:64

const ERROR_BODY_PREVIEW_BYTES: usize = 200;

/// Newest dated MCP protocol revision Codewhale advertises at `initialize` and
/// answers as an MCP server. Matches the shared MCP crate (`crates/mcp`).
pub(crate) const MCP_PROTOCOL_VERSION: &str = "2025-06-18";
/// Dated MCP revisions accepted during negotiation, newest first. A peer
/// answering or requesting any of these continues the handshake.
pub(crate) const MCP_SUPPORTED_PROTOCOL_VERSIONS: &[&str] =
    &[MCP_PROTOCOL_VERSION, "2025-03-26", "2024-11-05"];

fn validate_mcp_config_path(path: &Path) -> Result<()> {
    if path.as_os_str().is_empty() {
        anyhow::bail!("MCP config path cannot be empty");
    }
    if path
        .components()
        .any(|component| matches!(component, Component::ParentDir))
    {
        anyhow::bail!("MCP config path cannot contain '..' components");
    }
    Ok(())
}

/// Expand `${NAME}` placeholders in an MCP config value from the process
/// environment. This lets secrets (API keys, bearer tokens, …) be supplied
/// through environment variables instead of being written in cleartext into
/// the MCP config file on disk.
///
/// On a missing or malformed placeholder the error names only the offending
/// variable, never the surrounding value, so a secret-bearing string is never
/// echoed into logs or error output.
fn expand_env_placeholders_with(
    value: &str,
    environment: Option<&crate::plugins::HostEnvironment>,
) -> Result<String> {
    let mut out = String::new();
    let mut rest = value;

View on GitHub (pinned to 73e0f67d83)