{"record":{"id":"4e7bc2b632abf4ec","repo":"zeroclaw-labs/zeroclaw","slug":"manifest-exec-binary-escapes-plugin-directory","errorCode":null,"errorMessage":"manifest exec binary escapes plugin directory: {} is not under {}","messagePattern":"manifest exec binary escapes plugin directory: (.+?) is not under (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-hardware/src/loader.rs","lineNumber":229,"sourceCode":"    }\n    let binary_path = raw_binary_path.canonicalize().map_err(|e| {\n        ::zeroclaw_log::record!(\n            WARN,\n            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                .with_attrs(::serde_json::json!({\n                    \"binary_path\": raw_binary_path.display().to_string(),\n                    \"error\": format!(\"{}\", e),\n                })),\n            \"cannot canonicalize plugin binary path\"\n        );\n        anyhow::Error::msg(format!(\n            \"cannot canonicalize binary path {}: {e}\",\n            raw_binary_path.display()\n        ))\n    })?;\n    if !binary_path.starts_with(&canonical_plugin_dir) {\n        anyhow::bail!(\n            \"manifest exec binary escapes plugin directory: {} is not under {}\",\n            binary_path.display().to_string(),\n            canonical_plugin_dir.display()\n        );\n    }\n    if !binary_path.is_file() {\n        anyhow::bail!(\n            \"manifest exec binary is not a regular file: {}\",\n            binary_path.display()\n        );\n    }\n\n    let name = manifest.tool.name.clone();\n    let version = manifest.tool.version.clone();\n    let tool: Box<dyn Tool> = Box::new(SubprocessTool::new(manifest, binary_path));\n\n    Ok(LoadedPlugin {\n        name,","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-hardware/src/loader.rs#L211-L247","documentation":"After canonicalizing both paths, `load_one_plugin` enforces that the executable resolves inside the plugin directory; a `../` path or a symlink whose target lives outside the plugin dir makes containment fail and the manifest is rejected. This is a deliberate security guard: a plugin manifest must not be able to execute arbitrary files outside its own folder. The message prints both the canonical binary path and the canonical plugin dir for comparison.","triggerScenarios":"`binary = \"../../usr/bin/curl\"`; `binary` naming a symlink inside the plugin dir whose target is elsewhere (canonicalize resolves it); unusual dev setups where the plugin dir itself is only reachable through a symlink so canonical prefixes diverge.","commonSituations":"Trying to reuse a system binary from a plugin; symlinks created by package managers; plugin directories relocated across mounts.","solutions":["Copy the real executable into the plugin directory and point `binary` at the copy","Remove out-of-tree symlinks so the binary is a real file under the plugin dir","Never author `../` paths in `binary` — they are rejected by design"],"exampleFix":"# before\n[exec]\nbinary = \"../../../usr/local/bin/my-tool\"\n# after — vendored copy inside the plugin dir\n[exec]\nbinary = \"bin/my-tool\"","handlingStrategy":"validation","validationCode":"let canonical_dir = plugin_dir.canonicalize()?;\nlet binary = plugin_dir.join(&manifest.exec.binary);\nif binary.exists() {\n    let canonical_binary = binary.canonicalize()?;\n    if !canonical_binary.starts_with(&canonical_dir) {\n        anyhow::bail!(\"binary escapes plugin dir — vendors must ship inside the plugin\");\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Vendor every plugin executable inside the plugin directory; never reference system binaries","Avoid symlinks in plugin dirs — canonicalize resolves them and containment fails","Run the same containment check in your packaging step so bad plugins never reach the loader"],"tags":["hardware","plugin","manifest","security","path-traversal","loader"],"backgroundTag":"path-traversal-blocked","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}