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
Before invoking a reviewed-plugin MCP tool, string arguments that look like paths are resolved against the runtime cwd, canonicalized ('frozen'), and checked to still sit under the plugin's staged root. If the canonical path escapes that root, the call is rejected to stop reviewed plugins from reading or writing outside their sandbox.
Solutions
- Pass only paths that live under the plugin's staged root
- Remove '..' segments or absolute paths that resolve outside the staged directory
- If the file legitimately must be accessed, have the harness stage a copy into the staged root first
Example fix
// before
args.path = "/etc/passwd";
// after
let staged = staged_root.join("input.txt");
std::fs::copy("/etc/passwd", &staged)?;
args.path = staged.display().to_string(); Defensive patterns
Strategy: validation
Validate before calling
let frozen = candidate.canonicalize()?;
if !frozen.starts_with(staged_root) {
return Err("argument path escapes staged root".into());
} Type guard
fn within_staged_root(p: &Path, staged_root: &Path) -> bool {
p.canonicalize().map(|f| f.starts_with(staged_root)).unwrap_or(false)
} Try / catch
match call_plugin_tool(&route, &mut args).await {
Err(e) if e.to_string().contains("escaped its staged root") => {
eprintln!("stage required files under the plugin root before calling");
}
other => other,
} Prevention
- Only pass paths that already live under the staged root
- Canonicalize user-supplied paths and check prefix before invoking reviewed plugins
- Treat symlinks inside the staged root as untrusted and re-stage real files
When it happens
Trigger: A path-like argument containing '..' segments, absolute paths, or symlink targets that canonicalize to a location outside staged_root while candidate.exists() is true.
Common situations: Passing ~/.. or /etc paths to a reviewed plugin tool; a symlink inside the staged root pointing outward; the plugin expecting access to a file outside its staging area by design mistake.
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
- Codewhale-owned OAuth path escaped the credentials directory
- Codewhale-owned OAuth path has an invalid basename
- external credential path contains reparse point
- external credential path escapes its absolute root
- external credential path must be lexically normalized
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/f0f360cbec3994c1.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/mcp.rs:5578
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 73e0f67d83)