Hmbown/CodeWhale · error

MCP config path cannot contain '..' components

Error message

MCP config path cannot contain '..' components

What it means

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.

Source

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

use self::stdio::{STDIO_SHUTDOWN_GRACE, StderrTail};
use self::wire::{is_mcp_stale_session_body, is_mcp_stale_session_error};
use crate::network_policy::{Decision, NetworkPolicyDecider, host_from_url};
use crate::utils::write_atomic;

// === Error diagnostics helpers (#71) ===

/// Bytes of a non-2xx response body to surface in connection errors.
const ERROR_BODY_PREVIEW_BYTES: usize = 200;

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 8880682c63)

Solutions

  1. Use an absolute path written out in full, with no '..' segments
  2. Normalize the path first (canonicalize, or lexical normalization) so it contains no ParentDir components, then pass the normalized form
  3. Restructure the setup so the MCP config lives at a fixed location referenced absolutely

Example fix

// before
let p = config_root.join("../../../shared/mcp.json"); // contains ParentDir
// after: normalize lexically to an absolute, '..'-free path
let p = lexically_normalize(config_root.join("../../../shared/mcp.json"));
Defensive patterns

Strategy: validation

Validate before calling

// Reject or normalize before handing the path to the MCP layer
use std::path::Component;
fn safe_mcp_path(p: &std::path::Path) -> Option<std::path::PathBuf> {
    if p.as_os_str().is_empty() {
        return None;
    }
    let mut out = std::path::PathBuf::new();
    for c in p.components() {
        match c {
            Component::ParentDir => return None, // or lexically pop()
            other => out.push(other.as_os_str()),
        }
    }
    Some(out)
}

Prevention

When it happens

Trigger: Config paths like ../shared/mcp.json, /etc/codewhale/../../home/user/mcp.json, or paths built by joining user input that carries '..' segments.

Common situations: Sharing configs across repos with ../../-style relative paths, dotfile or symlink layouts expressed with '..', automation tools emitting paths with redundant parent segments.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/3542e71be4719ae9. Report an issue: GitHub.