libnyanpasu/clash-nyanpasu · error

journal source is not a regular file: {}

Error message

journal source is not a regular file: {}

What it means

rename_journal_same_filesystem inspects the journal source with symlink_metadata and rejects any node that is a symlink/reparse point or not a regular file before renaming it. Like the journal-read check, this defends the atomic rename protocol against following attacker-controlled links or renaming directories/special nodes into journal positions.

Source

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

            if duplicate_journal != journal {
                bail!(
                    "materialization operation has conflicting journal payloads in {duplicate_location:?}"
                );
            }
            Self::remove_private_regular(&duplicate_path)?;
        }
        Ok(Some((location, journal)))
    }

    /// The journal locations share one private root, so Unix `rename` is an
    /// atomic same-filesystem phase transition. Do not use `move_atomic`: its
    /// hard-link/unlink fallback can leave duplicate phase artifacts.
    #[allow(dead_code)]
    fn rename_journal_same_filesystem(source: &Path, destination: &Path) -> anyhow::Result<()> {
        let metadata = std::fs::symlink_metadata(source)
            .with_context(|| format!("inspect journal source {}", source.display()))?;
        if is_symlink_or_reparse(&metadata) || !metadata.is_file() {
            bail!("journal source is not a regular file: {}", source.display());
        }
        std::fs::rename(source, destination).with_context(|| {
            format!(
                "atomically rename journal {} -> {}",
                source.display(),
                destination.display()
            )
        })?;
        sync_directory(source.parent().expect("journal source has parent"))?;
        if source.parent() != destination.parent() {
            sync_directory(
                destination
                    .parent()
                    .expect("journal destination has parent"),
            )?;
        }
        Ok(())
    }

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Verify the source path passed in is the actual journal file path, not a parent directory or link.
  2. Remove the non-regular node (after confirming it is not legitimate) and recreate the journal via the normal write path.
  3. Restrict staging-root permissions so only the app user can create nodes there.
  4. Note: this function is currently #[allow(dead_code)]; if you hit this in tests or new call sites, ensure callers validate node type before invoking.

Example fix

// before
rename_journal_same_filesystem(&dir_path, &dest)?; // dir passed, not file
// after
let meta = std::fs::symlink_metadata(&journal_file)?;
assert!(meta.is_file(), "journal source must be a regular file");
rename_journal_same_filesystem(&journal_file, &dest)?;
Defensive patterns

Strategy: validation

Validate before calling

let meta = std::fs::symlink_metadata(&source)?;
if meta.file_type().is_symlink() || !meta.is_file() {
    bail!("refusing to rename non-regular journal source: {}", source.display());
}

Type guard

fn is_regular_file_node(p: &Path) -> bool {
    std::fs::symlink_metadata(p).map(|m| m.is_file()).unwrap_or(false)
}

Try / catch

match rename_journal_same_filesystem(&src, &dst) {
    Err(e) if e.to_string().contains("not a regular file") => {
        // inspect the node; remove if it's not legitimate, then recreate journal
        bail!("journal source invalid, operation must be redone")
    }
    r => r,
}

Prevention

When it happens

Trigger: rename_journal_same_filesystem called with a source path that is a symlink, directory, or device node — e.g. a planted symlink at the journal location in a shared directory, or a caller passing a wrong path that happens to exist as a directory.

Common situations: Untrusted processes writing into a world-writable staging root; refactored callers passing directory paths instead of journal file paths; filesystems materializing links as other node types.

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/57154bec3f23c4cb. Report an issue: GitHub.