sxyazi/yazi · error · io::Error

InvalidInput

InvalidInput

Error message

invalid trash entry path

What it means

DsStore::join rebuilds a macOS trashed item's original absolute path by joining a relative entry under the put-back location from .DS_Store. The relative path must be relative and free of ".." components; absolute paths or parent-directory traversals are rejected with this InvalidInput error to prevent path traversal when restoring.

Source

Thrown at yazi-fs/src/trash/macos/ds_store.rs:38

			let Value::Ustr(value) = record.value else { continue };
			if value.is_empty() {
				continue;
			}

			let location = locations.entry_ref(OsStr::new(&record.name)).or_default();
			match &record.field.fourcc().bytes() {
				b"ptbL" => location.parent = Some(value.into()),
				b"ptbN" => location.name = Some(value.into()),
				_ => {}
			}
		}

		Ok(locations)
	}

	pub(super) fn join(&self, rel: &Path) -> io::Result<PathBuf> {
		if !rel.is_relative() || rel.has_parent_component() {
			return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid trash entry path"));
		}

		let parent = self.parent.as_deref().ok_or_else(|| {
			io::Error::new(io::ErrorKind::InvalidData, "trash item has no put-back location")
		})?;

		let name = self.name.as_deref().ok_or_else(|| {
			io::Error::new(io::ErrorKind::InvalidData, "trash item has no put-back name")
		})?;

		let mut components = Path::new(name).components();
		if !matches!(components.next(), Some(Component::Normal(_))) || components.next().is_some() {
			return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid trash put-back name"));
		}

		let top_path = Path::new("/").join(parent).join(name);
		Ok(if rel.as_os_str().is_empty() { top_path } else { top_path.join(rel) })
	}

View on GitHub (pinned to 8ebf930f17)

Solutions

  1. Pass rel as the item's path relative to the trash directory (e.g. "folder/file.txt"), not an absolute path.
  2. Strip or reject any ".." components before calling join; recompute the relative path from the actual trash root.
  3. Ensure the item being restored actually resides inside the macOS trash so the relative computation is valid.

Example fix

// before
store.join(Path::new("/Users/alice/file.txt"))?
// after
let rel = item_path.strip_prefix(trash_dir)?;
store.join(rel)?
Defensive patterns

Strategy: validation

Validate before calling

fn rel_is_safe(rel: &std::path::Path) -> bool {
    rel.is_relative()
        && !rel.components().any(|c| matches!(c, std::path::Component::ParentDir | std::path::Component::RootDir))
}

Try / catch

match ds_store.join(&rel) {
    Ok(dest) => restore_to(dest),
    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => eprintln!("refusing non-relative or traversing trash entry path"),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling join with an absolute rel path (e.g. "/foo/bar") or one containing ".." components (e.g. "../escape"); rel comes from the trashed item's path relative to ~/.Trash, so only malformed caller input or tampered layout triggers it.

Common situations: Custom restore logic passing full absolute paths instead of Trash-relative ones; items moved between trash folders so the relative computation is wrong; adversarial paths crafted to escape the trash.

Related errors


AI-assisted analysis of sxyazi/yazi@8ebf930f17 (2026-09-02). Data as JSON: /api/errors/ebe417abf5a2480c. Report an issue: GitHub.