zeroclaw-labs/zeroclaw · error · anyhow::Error
unsafe SOP path component '{}'
Error message
unsafe SOP path component '{}' What it means
resolve_existing_ancestor walks a path upward via file_name()/parent(), collecting the existing part of the path, and requires every collected final component to be a Normal component (a plain name). This error means a component being resolved is not a normal file name - it is '..', '.', a root separator, or a Windows prefix. It is a low-level guard inside ensure_within_root that rejects structurally unsafe paths before any symlink resolution.
Source
Thrown at crates/zeroclaw-runtime/src/sop/procedural_memory.rs:431
let mut remainder: Vec<&std::ffi::OsStr> = Vec::new();
let mut current = path;
loop {
if current.exists() {
let mut resolved = fs::canonicalize(current)
.with_context(|| format!("canonicalize '{}'", current.display()))?;
for name in remainder.iter().rev() {
resolved.push(name);
}
return Ok(resolved);
}
match (current.file_name(), current.parent()) {
(Some(name), Some(parent)) => {
let component = Path::new(name);
if component
.components()
.any(|c| !matches!(c, Component::Normal(_)))
{
bail!("unsafe SOP path component '{}'", name.to_string_lossy());
}
remainder.push(name);
current = parent;
}
_ => bail!("cannot resolve SOP path '{}'", path.display()),
}
}
}
fn ensure_relative_component(component: &str) -> Result<()> {
let path = Path::new(component);
if path
.components()
.any(|c| !matches!(c, Component::Normal(_)))
{
bail!("unsafe SOP path component");
}
Ok(())View on GitHub (pinned to 88bb9c8533)
Solutions
- Fix the sops_dir (and install_root) configuration to be a normal absolute path with no '.', '..', or trailing-separator-only components
- Normalize candidate paths with std::path::PathBuf components and reject non-Normal components before calling apply_proposal
- On Windows, drop the drive-prefix form and pass a plain rooted path
Example fix
# before (config) sops_dir = "../sops/.." # after sops_dir = "/home/user/.zeroclaw/sops"
Defensive patterns
Strategy: validation
Validate before calling
fn all_normal_components(p: &Path) -> bool {
p.components().all(|c| matches!(c, std::path::Component::Normal(_)))
} Try / catch
Err(e) if e.to_string().contains("unsafe SOP path component") => {
// normalize the configured path (remove '..', '.', trailing separators) and retry once
} Prevention
- Normalize config paths with components().collect() and reject non-Normal parts at load time
- Never build SOP paths via string concatenation of user input
- Validate sops_dir/install_root in config loading, not at write time
When it happens
Trigger: contained_sop_dir/ensure_within_root receiving a sops_root or target whose final component is '..' or '.' (path literally ending in '../..', '/.', etc.) or carries a prefix like 'C:\' - typically from a sops_dir config value like '/', '..', 'C:\sops\', or from a slug/path that survived earlier checks but ends in a dot component.
Common situations: Misconfigured sops_dir in the ZeroClaw config (relative '..' entries, bare drive prefix on Windows), or hand-constructed paths passed through install_root that end in a dot or dot-dot segment.
Related errors
- target SOP directory '{}' escapes SOPs root
- cannot resolve SOP path '{}'
- Edge TTS binary_path must be a bare command name without pat
- webhook channel requires a `secret` configured for request a
- attachment path {} canonicalizes to {} which escapes workspa
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c32fff06126769e3.
Report an issue: GitHub.