ultraworkers/claw-code · error · std::io::Error
path {} escapes workspace boundary {}
Error message
path {} escapes workspace boundary {} What it means
`validate_workspace_boundary` (runtime/src/file_ops.rs:44) enforces the workspace-containment invariant for the `*_in_workspace` file operations: the resolved path must have the workspace root as a Path-component prefix (`Path::starts_with` is component-based, so `spacex` does NOT sneak past root `space`). Violations return `ErrorKind::PermissionDenied`. It is called from grep/glob/search and the in-workspace write variants (file_ops.rs:325, 348, 361, 407, 436, 686, 699, 714).
Source
Thrown at rust/crates/runtime/src/file_ops.rs:44
];
/// Check whether a file appears to contain binary content by examining
/// the first chunk for NUL bytes.
fn is_binary_file(path: &Path) -> io::Result<bool> {
use std::io::Read;
let mut file = fs::File::open(path)?;
let mut buffer = [0u8; 8192];
let bytes_read = file.read(&mut buffer)?;
Ok(buffer[..bytes_read].contains(&0))
}
/// Validate that a resolved path stays within the given workspace root.
/// Returns the canonical path on success, or an error if the path escapes
/// the workspace boundary (e.g. via `../` traversal or symlink).
#[allow(dead_code)]
fn validate_workspace_boundary(resolved: &Path, workspace_root: &Path) -> io::Result<()> {
if !resolved.starts_with(workspace_root) {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"path {} escapes workspace boundary {}",
resolved.display(),
workspace_root.display()
),
));
}
Ok(())
}
/// Text payload returned by file-reading operations.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TextFilePayload {
#[serde(rename = "filePath")]
pub file_path: String,
pub content: String,
#[serde(rename = "numLines")]View on GitHub (pinned to 08106b0c37)
Solutions
- Keep every target path inside the workspace root; drop `../` segments from patterns.
- Replace out-of-boundary symlinks with copies, or move the referenced file inside the workspace.
- When calling the `_in_workspace` APIs programmatically, canonicalize the root (fs::canonicalize) before passing it so prefix comparison matches canonicalized targets.
Example fix
// before
let root = Path::new("~/proj"); // tilde never expands -> every check fails
write_file_in_workspace("/etc/app.conf", data, root)?; // path ... escapes workspace boundary ...
// after
let root = std::fs::canonicalize(shellexpand_home("~/proj"))?;
let inside = root.join("config/app.conf");
write_file_in_workspace(&inside.to_string_lossy(), data, &root)?; Defensive patterns
Strategy: validation
Validate before calling
fn inside_workspace(target: &Path, root: &Path) -> io::Result<bool> {
let root = std::fs::canonicalize(root)?;
let target = std::fs::canonicalize(target)?;
Ok(target.starts_with(&root)) // component-wise prefix, like the guard
} Try / catch
match write_file_in_workspace(path, data, root) {
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied
&& e.to_string().contains("escapes workspace boundary") => { /* reject pattern / move file inside root */ }
other => other,
} Prevention
- Canonicalize the workspace root before passing it to *_in_workspace APIs
- Avoid `../` in glob/search patterns
- Audit symlinks inside the workspace (pnpm/bun stores) — they canonicalize outside the root
- Component prefix means sibling dirs like proj and proj-2 do NOT match; only real containment passes
When it happens
Trigger: A glob or search pattern containing `../` that resolves outside the root; a symlink inside the workspace pointing to a directory outside it; passing an absolute path in `/tmp` or `$HOME` to an in-workspace op; the workspace root itself being non-canonical (given root `~/proj` while resolved paths canonicalize through a symlink to `/home/user/proj`).
Common situations: Monorepos where `node_modules` symlinks point to global package stores; `bun`/`pnpm` symlink farms; configs referencing files outside the project; running claw from a symlinked path so the passed root never prefix-matches canonicalized targets.
Related errors
AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18).
Data as JSON: /api/errors/bd4ce21d994126c5.
Report an issue: GitHub.