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
- Remove or fix the journal file so `managed_path` points at the real managed profile location, outside the materialization root
- Never hand-write journals; let the application create them
- If tampering is suspected, wipe the materialization directory and re-materialize profiles from sources
- 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
- Never hand-edit journal YAML
- Keep managed paths outside the materialization root
- Restrict write access to the materialization directory
- When constructing managed paths, resolve and verify no component equals reserved root names
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
- runtime candidate directory is a symlink or reparse point: {
- profile directory is a symlink, reparse point, or non-direct
- materialization journal is not a regular file: {}
- private materialization artifact is not a regular file: {}
- journal source is not a regular file: {}
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/38c0dcac8963fc11.
Report an issue: GitHub.