{"record":{"id":"f0f360cbec3994c1","repo":"Hmbown/CodeWhale","slug":"reviewed-plugin-mcp-argument-path-escaped-its-stag","errorCode":null,"errorMessage":"reviewed plugin MCP argument path escaped its staged root","messagePattern":"reviewed plugin MCP argument path escaped its staged root","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp.rs","lineNumber":5578,"sourceCode":"fn freeze_plugin_stdio_paths(config: &mut McpServerConfig, staged_root: &Path) -> Result<()> {\n    if let Some(command) = config.command.as_mut()\n        && (command.contains('/') || command.contains('\\\\'))\n    {\n        let frozen = resolve_plugin_mcp_cwd(staged_root, Some(Path::new(command)))?;\n        *command = frozen.display().to_string();\n    }\n    let runtime_cwd = config.cwd.as_deref().unwrap_or(staged_root).to_path_buf();\n    for argument in &mut config.args {\n        if argument.starts_with('-') || Path::new(argument).is_absolute() {\n            continue;\n        }\n        let candidate = normalize_path_components(&runtime_cwd.join(argument.as_str()));\n        if candidate.exists() {\n            let frozen = candidate\n                .canonicalize()\n                .context(\"failed to freeze reviewed plugin MCP argument path\")?;\n            if !frozen.starts_with(staged_root) {\n                anyhow::bail!(\"reviewed plugin MCP argument path escaped its staged root\");\n            }\n            *argument = frozen.display().to_string();\n        }\n    }\n    Ok(())\n}\n\nfn resolve_plugin_mcp_cwd(plugin_path: &Path, cwd: Option<&Path>) -> Result<PathBuf> {\n    let cwd = match cwd {\n        Some(cwd) if cwd.is_relative() => normalize_path_components(&plugin_path.join(cwd)),\n        Some(cwd) => normalize_path_components(cwd),\n        None => plugin_path.to_path_buf(),\n    };\n    let resolved = cwd\n        .canonicalize()\n        .unwrap_or_else(|_| normalize_path_components(&cwd));\n    if !resolved.starts_with(plugin_path) {\n        anyhow::bail!(\"reviewed plugin MCP path escaped its staged root\");","sourceCodeStart":5560,"sourceCodeEnd":5596,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp.rs#L5560-L5596","documentation":"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.","triggerScenarios":"A path-like argument containing '..' segments, absolute paths, or symlink targets that canonicalize to a location outside staged_root while candidate.exists() is true.","commonSituations":"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.","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"],"exampleFix":"// before\nargs.path = \"/etc/passwd\";\n// after\nlet staged = staged_root.join(\"input.txt\");\nstd::fs::copy(\"/etc/passwd\", &staged)?;\nargs.path = staged.display().to_string();","handlingStrategy":"validation","validationCode":"let frozen = candidate.canonicalize()?;\nif !frozen.starts_with(staged_root) {\n    return Err(\"argument path escapes staged root\".into());\n}","typeGuard":"fn within_staged_root(p: &Path, staged_root: &Path) -> bool {\n    p.canonicalize().map(|f| f.starts_with(staged_root)).unwrap_or(false)\n}","tryCatchPattern":"match call_plugin_tool(&route, &mut args).await {\n    Err(e) if e.to_string().contains(\"escaped its staged root\") => {\n        eprintln!(\"stage required files under the plugin root before calling\");\n    }\n    other => other,\n}","preventionTips":["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"],"tags":["mcp","security","sandbox","path-traversal"],"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"}