zeroclaw-labs/zeroclaw · error · anyhow::Error

Path not allowed: contains null byte

Error message

Path not allowed: contains null byte

What it means

candidate_path, the shared pre-check for worktree add and remove targets, rejects any raw_path containing a NUL byte (git_operations.rs:109-110). A NUL cannot appear in a legal filesystem path and is the classic marker of corrupted input — a truncated C string, a binary blob leaked into JSON, or an LLM emitting \u0000 — so it is refused before any filesystem access happens.

Source

Thrown at crates/zeroclaw-tools/src/git_operations.rs:110

                    anyhow::Error::msg(format!("Cannot resolve path '{}': {}", p, e))
                })?;
                let workspace_canonical = self
                    .workspace_dir
                    .canonicalize()
                    .unwrap_or_else(|_| self.workspace_dir.clone());
                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)?;

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Sanitize the path upstream: strip or reject control characters before the tool call.
  2. Fix the source of the string — usually JSON decoding or buffer slicing that let \u0000 through.
  3. Re-issue the worktree call with a clean, relative path under the workspace.

Example fix

// before
worktree(op: "add", path: ".worktrees/feat\u0000")
// -> Path not allowed: contains null byte

// after
worktree(op: "add", path: ".worktrees/feat")
Defensive patterns

Strategy: validation

Validate before calling

fn path_is_clean(raw: &str) -> bool {
    !raw.contains('\0')
}
assert!(path_is_clean(worktree_path), "worktree path contains a null byte");

Type guard

fn path_is_clean(raw: &str) -> bool { !raw.contains('\0') }

Try / catch

match git_tool.execute(params).await {
    Err(e) if e.to_string().contains("contains null byte") => {
        // input corruption: strip control characters and retry once with the
        // sanitized string; if another NUL appears, reject the input source
    }
    r => r,
}

Prevention

When it happens

Trigger: Calling the git tool's worktree operation with a path string that embeds a NUL, such as ".worktrees/feat\u0000", a value decoded from malformed JSON with embedded control characters, or a path copied from a buffer that was not NUL-terminated and sliced past its end.

Common situations: Prompt-injection or fuzzing payloads reaching tool args; deserialized JSON where \u0000 survived into a Rust String; copy-paste from logs containing binary garbage; downstream systems that build paths from fixed-size buffers.

Related errors


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