Hmbown/CodeWhale · error

reviewed plugin MCP argument path escaped its staged root

Error message

reviewed plugin MCP argument path escaped its staged root

What it means

For reviewed (sandboxed) plugin MCP servers, any relative command argument that names an existing path is resolved against the runtime cwd, canonicalized, and then required to stay inside the plugin's staged root directory. If the canonical path (after resolving symlinks and '..') lands outside the staging directory, the server config is rejected - a reviewed plugin cannot smuggle execution outside its staged tree via argument paths.

Source

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

fn freeze_plugin_stdio_paths(config: &mut McpServerConfig, staged_root: &Path) -> Result<()> {
    if let Some(command) = config.command.as_mut()
        && (command.contains('/') || command.contains('\\'))
    {
        let frozen = resolve_plugin_mcp_cwd(staged_root, Some(Path::new(command)))?;
        *command = frozen.display().to_string();
    }
    let runtime_cwd = config.cwd.as_deref().unwrap_or(staged_root).to_path_buf();
    for argument in &mut config.args {
        if argument.starts_with('-') || Path::new(argument).is_absolute() {
            continue;
        }
        let candidate = normalize_path_components(&runtime_cwd.join(argument.as_str()));
        if candidate.exists() {
            let frozen = candidate
                .canonicalize()
                .context("failed to freeze reviewed plugin MCP argument path")?;
            if !frozen.starts_with(staged_root) {
                anyhow::bail!("reviewed plugin MCP argument path escaped its staged root");
            }
            *argument = frozen.display().to_string();
        }
    }
    Ok(())
}

fn resolve_plugin_mcp_cwd(plugin_path: &Path, cwd: Option<&Path>) -> Result<PathBuf> {
    let cwd = match cwd {
        Some(cwd) if cwd.is_relative() => normalize_path_components(&plugin_path.join(cwd)),
        Some(cwd) => normalize_path_components(cwd),
        None => plugin_path.to_path_buf(),
    };
    let resolved = cwd
        .canonicalize()
        .unwrap_or_else(|_| normalize_path_components(&cwd));
    if !resolved.starts_with(plugin_path) {
        anyhow::bail!("reviewed plugin MCP path escaped its staged root");

View on GitHub (pinned to 8880682c63)

Solutions

  1. Change the plugin's args to reference files inside the plugin's staged directory only
  2. Remove symlinks inside the staged tree that point outside it, then re-stage and re-review the plugin
  3. Invoke external tools by bare name (PATH lookup) instead of file paths - PATH args skip the freeze check

Example fix

// before (plugin manifest, server args)
"args": ["../../bin/helper", "--serve"]

// after
"args": ["./bin/helper", "--serve"]
Defensive patterns

Strategy: validation

Validate before calling

// Before enabling a reviewed plugin MCP server, verify every relative arg stays in the staged root:
for arg in &server_config.args {
    if arg.starts_with('-') || std::path::Path::new(arg).is_absolute() { continue; }
    let candidate = runtime_cwd.join(arg);
    if candidate.exists() {
        let frozen = candidate.canonicalize()?;
        anyhow::ensure!(frozen.starts_with(&staged_root), "arg escapes staged root: {arg}");
    }
}

Type guard

fn plugin_args_within_root(args: &[String], cwd: &std::path::Path, root: &std::path::Path) -> bool {
    args.iter().all(|a| {
        a.starts_with('-')
            || std::path::Path::new(a).is_absolute()
            || !cwd.join(a).exists()
            || cwd.join(a).canonicalize().map(|c| c.starts_with(root)).unwrap_or(false)
    })
}

Prevention

When it happens

Trigger: A plugin manifest passes a relative arg like '../../bin/helper', or an arg that is or crosses a symlink pointing outside the staged root, and that path exists on disk so the freeze step runs.

Common situations: A plugin that invokes bundled binaries by relative path but was staged with symlinks inside the tree; moving the staging directory so previously-fine relative paths now resolve elsewhere.

Related errors


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