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

  1. Use a target directory inside the workspace, e.g. ".worktrees/feature-x".
  2. If the path came from joining inputs, normalize it first (Component-based rebuild or path-clean) so no `..` remains.
  3. 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

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/4e4a966c1e51f482. Report an issue: GitHub.