{"record":{"id":"7e1398838fd392f7","repo":"sxyazi/yazi","slug":"invalid-trash-entry-path","errorCode":null,"errorMessage":"invalid trash entry path","messagePattern":"invalid trash entry path","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"yazi-fs/src/trash/macos/ds_store.rs","lineNumber":38,"sourceCode":"\t\t\tlet Value::Ustr(value) = record.value else { continue };\n\t\t\tif value.is_empty() {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet location = locations.entry_ref(OsStr::new(&record.name)).or_default();\n\t\t\tmatch &record.field.fourcc().bytes() {\n\t\t\t\tb\"ptbL\" => location.parent = Some(value.into()),\n\t\t\t\tb\"ptbN\" => location.name = Some(value.into()),\n\t\t\t\t_ => {}\n\t\t\t}\n\t\t}\n\n\t\tOk(locations)\n\t}\n\n\tpub(super) fn join(&self, rel: &Path) -> io::Result<PathBuf> {\n\t\tif !rel.is_relative() || rel.has_parent_component() {\n\t\t\treturn Err(io::Error::new(io::ErrorKind::InvalidInput, \"invalid trash entry path\"));\n\t\t}\n\n\t\tlet parent = self.parent.as_deref().ok_or_else(|| {\n\t\t\tio::Error::new(io::ErrorKind::InvalidData, \"trash item has no put-back location\")\n\t\t})?;\n\n\t\tlet name = self.name.as_deref().ok_or_else(|| {\n\t\t\tio::Error::new(io::ErrorKind::InvalidData, \"trash item has no put-back name\")\n\t\t})?;\n\n\t\tlet mut components = Path::new(name).components();\n\t\tif !matches!(components.next(), Some(Component::Normal(_))) || components.next().is_some() {\n\t\t\treturn Err(io::Error::new(io::ErrorKind::InvalidData, \"invalid trash put-back name\"));\n\t\t}\n\n\t\tlet top_path = Path::new(\"/\").join(parent).join(name);\n\t\tOk(if rel.as_os_str().is_empty() { top_path } else { top_path.join(rel) })\n\t}","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/sxyazi/yazi/blob/94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2/yazi-fs/src/trash/macos/ds_store.rs#L20-L56","documentation":"`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.","triggerScenarios":"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.","commonSituations":"Programmatic child-id construction on macOS trash; stale expanded-trash state rehydrated with mangled rel paths.","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."],"exampleFix":"// before\nlet path = ds_store.join(Path::new(\"../escape\"))?; // InvalidInput\n\n// after\nuse yazi_shim::path::PathExt;\nlet rel = Path::new(\"photo.png\");\nassert!(rel.is_relative() && !rel.has_parent_component());\nlet path = ds_store.join(rel)?;","handlingStrategy":"validation","validationCode":"use yazi_shim::path::PathExt;\n\nif !rel.is_relative() || rel.has_parent_component() {\n    // reject: rel must be a single plain component (a file name from read_dir)\n}","typeGuard":"fn is_single_component(rel: &std::path::Path) -> bool {\n    use yazi_shim::path::PathExt;\n    rel.is_relative() && !rel.has_parent_component()\n}","tryCatchPattern":"match ds_store.join(rel) {\n    Ok(p) => { /* ... */ }\n    Err(e) if e.kind() == io::ErrorKind::InvalidInput => { /* rel malformed: rebuild from a fresh listing */ }\n    Err(e) => return Err(e),\n}","preventionTips":["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."],"tags":["trash","macos","ds-store","path-traversal","invalid-input"],"backgroundTag":null,"analyzedSha":"94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2","analyzedAt":"2026-08-16T09:56:24.836Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}