zeroclaw-labs/zeroclaw · error · anyhow::Error
Path not allowed: parent-directory traversal is not allowed
Error message
Path not allowed: parent-directory traversal is not allowed
What it means
candidate_path rejects worktree paths whose component list contains Component::ParentDir, i.e. any `..` segment (git_operations.rs:112-117). Unlike resolve_working_dir (which canonicalizes then compares), this check is lexical and fires before the path is joined onto workspace_dir, so even `..` sequences that would re-enter the workspace are refused for worktree targets.
Source
Thrown at crates/zeroclaw-tools/src/git_operations.rs:116
if !resolved.starts_with(&workspace_canonical) {
anyhow::bail!("Path '{}' resolves outside the workspace directory", p);
}
resolved
}
_ => self.workspace_dir.clone(),
};
Ok(base)
}
fn candidate_path(&self, raw_path: &str) -> anyhow::Result<PathBuf> {
if raw_path.contains('\0') {
anyhow::bail!("Path not allowed: contains null byte");
}
if Path::new(raw_path)
.components()
.any(|c| matches!(c, std::path::Component::ParentDir))
{
anyhow::bail!("Path not allowed: parent-directory traversal is not allowed");
}
let raw = Path::new(raw_path);
Ok(if raw.is_absolute() {
raw.to_path_buf()
} else {
self.workspace_dir.join(raw)
})
}
fn ensure_worktree_add_target_allowed(&self, raw_path: &str) -> anyhow::Result<PathBuf> {
let candidate = self.candidate_path(raw_path)?;
let parent = candidate.parent().ok_or_else(|| {
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
.with_outcome(::zeroclaw_log::EventOutcome::Failure)
.with_attrs(::serde_json::json!({"raw_path": raw_path})),View on GitHub (pinned to 88bb9c8533)
Solutions
- Use a target directory inside the workspace, e.g. ".worktrees/feature-x".
- If the path came from joining inputs, normalize it first (Component-based rebuild or path-clean) so no `..` remains.
- Restructure the workflow: create the worktree under the workspace and only symlink it outward if policy allows.
Example fix
// before worktree(op: "add", path: "../feature-x") // -> Path not allowed: parent-directory traversal is not allowed // after worktree(op: "add", path: ".worktrees/feature-x")
Defensive patterns
Strategy: validation
Validate before calling
use std::path::{Path, Component};
fn has_parent_traversal(raw: &str) -> bool {
Path::new(raw).components()
.any(|c| matches!(c, Component::ParentDir))
}
if has_parent_traversal(worktree_path) {
return Err("worktree path must not contain '..'".into());
} Type guard
fn is_traversal_free(raw: &str) -> bool {
!std::path::Path::new(raw).components()
.any(|c| matches!(c, std::path::Component::ParentDir))
} Try / catch
match git_tool.execute(params).await {
Err(e) if e.to_string().contains("parent-directory traversal") => {
// normalize the path (drop '..' segments lexically) and retry with
// the workspace-relative form — only if it denotes a location inside
// the workspace; otherwise report
}
r => r,
} Prevention
- Assemble worktree targets by joining the workspace root with a single name component
- Normalize user input before passing paths to tools
- Remember the check is lexical: even in-workspace .. re-entries are refused
When it happens
Trigger: Calling git worktree add/remove with paths like "../shared/wt", "logs/../../escape", or any absolute path containing a .. component. Any ParentDir component anywhere in raw_path triggers the bail before candidate_path builds the joined PathBuf at git_operations.rs:119-124.
Common situations: Agents following the common git habit `git worktree add ../feature-x`; paths assembled by concatenating a base dir with user input without normalization; toolchains that emit ..-relative paths when no explicit target is given.
Related errors
- invalid SOP name '{name}': must be a single path component (
- unsafe SOP path component
- Path not allowed: contains null byte
- Worktree path '{}' resolves outside the workspace or allowed
- Missing 'subcommand' parameter. Use: list, add, remove, prun
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/4e4a966c1e51f482.
Report an issue: GitHub.