{"record":{"id":"a0375eed363df0da","repo":"sxyazi/yazi","slug":"invalid-trash-entry-path-a0375e","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/trash_id.rs","lineNumber":26,"sourceCode":"pub(crate) struct TrashId {\n\ttop: PathBuf,\n\trel: PathBuf,\n}\n\nimpl TrashId {\n\tpub(super) fn new<T, R>(top: T, rel: R) -> io::Result<Self>\n\twhere\n\t\tT: Into<PathBuf>,\n\t\tR: Into<PathBuf>,\n\t{\n\t\tlet top = top.into();\n\t\tlet rel = rel.into();\n\n\t\tif top.as_os_str().is_empty() || !rel.is_relative() {\n\t\t\treturn Err(io::Error::new(io::ErrorKind::InvalidInput, \"invalid trash entry\"));\n\t\t}\n\t\tif 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\tOk(Self { top, rel })\n\t}\n\n\tpub(super) fn top(&self) -> &Path { &self.top }\n\n\tpub(super) fn rel(&self) -> &Path { &self.rel }\n\n\t#[cfg(target_os = \"macos\")]\n\tpub(super) fn path(&self) -> PathBuf {\n\t\tif self.has_rel() { self.top.join(&self.rel) } else { self.top.clone() }\n\t}\n\n\tpub(super) fn child(&self, name: &OsStr) -> io::Result<Self> {\n\t\tlet rel = self.rel.join(name);\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\"));","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/sxyazi/yazi/blob/94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2/yazi-fs/src/trash/trash_id.rs#L8-L44","documentation":"Returned by TrashId::new when rel is relative but contains parent components ('..'), which would let an id escape its trashed top-level item. This is an anti-traversal invariant enforced at id-construction time with io::ErrorKind::InvalidInput.","triggerScenarios":"rel = '../evil', rel = 'a/../../b', or any segment equal to '..' in the Lua table passed to Trash:entry.","commonSituations":"Plugins building rel by joining untrusted name strings; path normalization logic that leaves '..' segments; user input pasted into a trash navigation command.","solutions":["Reject or sanitize rel components equal to '..' before constructing the id.","Use only names returned by Trash:list (real file names cannot be '..').","Keep rel built exclusively by joining entry.child() results."],"exampleFix":"-- Lua: before\nlocal rel = user_input -- could be \"../..\"\nlocal e = ya.fs(\"trash\"):entry({ top = top, rel = rel })\n-- after\nlocal rel = user_input:gsub(\"[^/]+\", function(s) return s == \"..\" and \"\" or s end):gsub(\"/+\", \"/\")\nlocal e = ya.fs(\"trash\"):entry({ top = top, rel = rel })","handlingStrategy":"validation","validationCode":"-- Lua: strip parent components from rel\nlocal function sanitize_rel(rel)\n  local parts = {}\n  for seg in rel:gmatch(\"[^/]+\") do\n    if seg ~= \"..\" and seg ~= \".\" then table.insert(parts, seg) end\n  end\n  return table.concat(parts, \"/\")\nend","typeGuard":"local function is_safe_rel(rel)\n  for seg in rel:gmatch(\"[^/]+\") do if seg == \"..\" then return false end end\n  return rel:sub(1, 1) ~= \"/\"\nend","tryCatchPattern":"if not is_safe_rel(id.rel) then id.rel = sanitize_rel(id.rel) end\nlocal e = ya.fs(\"trash\"):entry(id)","preventionTips":["Treat rel built from user input as untrusted; filter '..' segments.","Build rel only by joining names returned from list().","Prefer entry.child-derived ids (the library maintains the invariant for you)."],"tags":["trash","trash-id","invalid-input","path-traversal","validation"],"backgroundTag":null,"analyzedSha":"94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2","analyzedAt":"2026-08-16T09:56:24.836Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}