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

  1. Fix the sops_dir (and install_root) configuration to be a normal absolute path with no '.', '..', or trailing-separator-only components
  2. Normalize candidate paths with std::path::PathBuf components and reject non-Normal components before calling apply_proposal
  3. 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

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


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