sxyazi/yazi · error · io::Error
invalid trash entry path
Error message
invalid trash entry path
What it means
`DsStore::join` (yazi-fs/src/trash/macos/ds_store.rs:38) rebuilds a put-back path for a macOS trashed item from the `.DS_Store` `ptbL`/`ptbN` records. The `rel` argument (the child path inside a trashed directory) must be a single relative component — not absolute and free of parent components. Anything else returns `ErrorKind::InvalidInput`, `invalid trash entry path`, preventing path traversal out of the trashed tree.
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 94abcfa92f)
Solutions
- Only pass child names obtained directly from listing the trashed directory (single `Normal` component).
- Validate `rel.is_relative() && !rel.has_parent_component()` before calling `join`.
- Rebuild child ids from a fresh listing instead of reusing persisted ones.
Example fix
// before
let path = ds_store.join(Path::new("../escape"))?; // InvalidInput
// after
use yazi_shim::path::PathExt;
let rel = Path::new("photo.png");
assert!(rel.is_relative() && !rel.has_parent_component());
let path = ds_store.join(rel)?; Defensive patterns
Strategy: validation
Validate before calling
use yazi_shim::path::PathExt;
if !rel.is_relative() || rel.has_parent_component() {
// reject: rel must be a single plain component (a file name from read_dir)
} Type guard
fn is_single_component(rel: &std::path::Path) -> bool {
use yazi_shim::path::PathExt;
rel.is_relative() && !rel.has_parent_component()
} Try / catch
match ds_store.join(rel) {
Ok(p) => { /* ... */ }
Err(e) if e.kind() == io::ErrorKind::InvalidInput => { /* rel malformed: rebuild from a fresh listing */ }
Err(e) => return Err(e),
} Prevention
- Only derive child rel paths from directory listing results (single Normal components).
- Reject `..` and multi-segment rels at id-construction time on macOS trash.
- Do not persist trash-child state across sessions; re-derive it by listing.
When it happens
Trigger: Calling `join` with a `rel` containing `..`, multiple segments (`a/b`), or an absolute path — typically from a `TrashId` whose rel was hand-built or persisted from bad state rather than derived from `read_dir` results.
Common situations: Programmatic child-id construction on macOS trash; stale expanded-trash state rehydrated with mangled rel paths.
Related errors
- invalid trash put-back name
- trash item has no put-back location
- trash item has no put-back name
- trash item is not a directory
- item is not in the trash
AI-assisted analysis of sxyazi/yazi@94abcfa92f (2026-08-16).
Data as JSON: /api/errors/7e1398838fd392f7.
Report an issue: GitHub.