zeroclaw-labs/zeroclaw · error
manifest exec binary escapes plugin directory
Error message
manifest exec binary escapes plugin directory: {} is not under {} What it means
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.
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
Example fix
# before [exec] binary = "../../../usr/local/bin/my-tool" # after — vendored copy inside the plugin dir [exec] binary = "bin/my-tool"
Defensive patterns
Strategy: validation
Validate before calling
let canonical_dir = plugin_dir.canonicalize()?;
let binary = plugin_dir.join(&manifest.exec.binary);
if binary.exists() {
let canonical_binary = binary.canonicalize()?;
if !canonical_binary.starts_with(&canonical_dir) {
anyhow::bail!("binary escapes plugin dir — vendors must ship inside the plugin");
}
} Prevention
- 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
When it happens
Trigger: `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.
Common situations: Trying to reuse a system binary from a plugin; symlinks created by package managers; plugin directories relocated across mounts.
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
- manifest exec binary is not a regular file
- manifest exec binary not found
- manifest missing [exec] binary
- manifest missing [tool] description
- manifest missing [tool] name
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/4e7bc2b632abf4ec.
Report an issue: GitHub.
Appendix: source
Thrown at crates/zeroclaw-hardware/src/loader.rs:229
}
let binary_path = raw_binary_path.canonicalize().map_err(|e| {
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
.with_outcome(::zeroclaw_log::EventOutcome::Failure)
.with_attrs(::serde_json::json!({
"binary_path": raw_binary_path.display().to_string(),
"error": format!("{}", e),
})),
"cannot canonicalize plugin binary path"
);
anyhow::Error::msg(format!(
"cannot canonicalize binary path {}: {e}",
raw_binary_path.display()
))
})?;
if !binary_path.starts_with(&canonical_plugin_dir) {
anyhow::bail!(
"manifest exec binary escapes plugin directory: {} is not under {}",
binary_path.display().to_string(),
canonical_plugin_dir.display()
);
}
if !binary_path.is_file() {
anyhow::bail!(
"manifest exec binary is not a regular file: {}",
binary_path.display()
);
}
let name = manifest.tool.name.clone();
let version = manifest.tool.version.clone();
let tool: Box<dyn Tool> = Box::new(SubprocessTool::new(manifest, binary_path));
Ok(LoadedPlugin {
name,View on GitHub (pinned to 88bb9c8533)