libnyanpasu/clash-nyanpasu · error

materialization journal targets reserved private storage

Error message

materialization journal targets reserved private storage

What it means

A parsed materialization journal's `managed_path` is checked component-by-component against `is_materialization_root_name`; if any path component names a reserved private storage directory (staging, backup, cleanup, etc.), the journal is rejected with this error. This prevents a malicious or corrupted journal from directing materialization logic to modify the library's own private state area — a path-traversal style protection.

Source

Thrown at backend/tauri/src/service/profile_file.rs:630

            bail!(
                "materialization journal is not a regular file: {}",
                path.display()
            );
        }
        let content = std::fs::read_to_string(path)
            .with_context(|| format!("read materialization journal {}", path.display()))?;
        let journal: MaterializationJournal = serde_yaml::from_str(&content)
            .with_context(|| format!("parse materialization journal {}", path.display()))?;
        if journal.operation_id != operation_id || !valid_operation_id(&journal.operation_id) {
            bail!("materialization journal operation id mismatch");
        }
        if journal
            .managed_path
            .as_path()
            .components()
            .any(|component| is_materialization_root_name(component.as_os_str()))
        {
            bail!("materialization journal targets reserved private storage");
        }
        if journal.hash.len() != 64 || !journal.hash.bytes().all(|byte| byte.is_ascii_hexdigit()) {
            bail!("materialization journal hash is invalid");
        }
        Ok(journal)
    }

    fn remove_nofollow(path: &Path) -> anyhow::Result<()> {
        match std::fs::symlink_metadata(path) {
            Ok(metadata) if metadata.is_dir() && !is_symlink_or_reparse(&metadata) => {
                bail!(
                    "refusing to remove directory as a profile resource: {}",
                    path.display()
                )
            }
            Ok(_) => {
                std::fs::remove_file(path)
                    .with_context(|| format!("remove profile resource {}", path.display()))?;

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Remove or fix the journal file so `managed_path` points at the real managed profile location, outside the materialization root
  2. Never hand-write journals; let the application create them
  3. If tampering is suspected, wipe the materialization directory and re-materialize profiles from sources
  4. Keep the materialization root not writable by untrusted users/processes

Example fix

// before (journal YAML)
// managed_path: staging/profiles/config.yaml
managed_path: profiles/config.yaml
// after: managed path outside reserved roots (staging/, backup/, cleanup/)
Defensive patterns

Strategy: validation

Validate before calling

fn targets_reserved(p: &std::path::Path, reserved: &[&str]) -> bool {
    p.components().any(|c| {
        let s = c.as_os_str().to_string_lossy();
        reserved.iter().any(|r| s.eq_ignore_ascii_case(r))
    })
}
// reject any candidate managed_path where targets_reserved(...) is true before writing a journal

Type guard

fn safe_managed_path(p: &std::path::Path, reserved: &[&str]) -> bool {
    !targets_reserved(p, reserved)
}

Try / catch

match read_journal(&path, &op_id) {
    Err(e) if e.to_string().contains("reserved private storage") => {
        // journal is hostile/corrupt: quarantine and re-materialize
        let _ = std::fs::remove_file(&path);
    }
    Err(e) => return Err(e),
    Ok(j) => use_journal(j),
}

Prevention

When it happens

Trigger: `read_journal` parses a journal whose `managed_path` contains a reserved root name as any component, e.g. `managed_path: staging/profiles/config.yaml` or `.../backup/foo`. Produced by hand-crafted or tampered journal YAML, or a bug writing journals with private paths.

Common situations: Manually edited journal files pointing managed paths into the materialization root; an attacker-supplied or sync-corrupted journal; misconfigured relative paths that accidentally resolve under the private root.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/38c0dcac8963fc11. Report an issue: GitHub.