{"record":{"id":"068c742e6dcc68a7","repo":"denoland/deno","slug":"refusing-zip-entry-with-unsafe-path","errorCode":null,"errorMessage":"refusing zip entry with unsafe path: {}","messagePattern":"refusing zip entry with unsafe path: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"cli/tools/desktop.rs","lineNumber":2273,"sourceCode":"          let mut perms = meta.permissions();\n          perms.set_mode(safe);\n          let _ = std::fs::set_permissions(&dest_path, perms);\n        }\n      }\n    }\n  } else if name.ends_with(\".zip\") {\n    let mut archive = zip::ZipArchive::new(std::io::Cursor::new(data))?;\n    // Iterate entries manually rather than `archive.extract(dest)`: that\n    // helper has the same shape as the tar `unpack` we deliberately\n    // avoided (no perm masking; no defence-in-depth against zip-slip\n    // beyond the crate's own checks). Treat the archive as untrusted.\n    for i in 0..archive.len() {\n      let mut entry = archive.by_index(i)?;\n      // `enclosed_name` rejects drive labels, absolute paths and `..`\n      // components. Anything that fails this check is a zip-slip attempt\n      // (or a legitimately weird archive we don't want to handle).\n      let Some(rel_path) = entry.enclosed_name() else {\n        bail!(\"refusing zip entry with unsafe path: {}\", entry.name());\n      };\n      // Defence in depth — re-check the components ourselves.\n      if rel_path.components().any(|c| {\n        matches!(\n          c,\n          std::path::Component::ParentDir | std::path::Component::RootDir\n        )\n      }) {\n        bail!(\n          \"refusing zip entry with traversal path: {}\",\n          rel_path.display()\n        );\n      }\n      // Refuse symlinks: with prior entries already extracted, a\n      // symlink-then-write pair is the standard zip-slip-via-symlink\n      // escape, and LAUFEY Windows archives have no legitimate need for\n      // them.\n      if entry.is_symlink() {","sourceCodeStart":2255,"sourceCodeEnd":2291,"githubUrl":"https://github.com/denoland/deno/blob/f7822238cab635a3a19f99f493f675fa81a7f9d8/cli/tools/desktop.rs#L2255-L2291","documentation":"Thrown while extracting a LAUFEY Windows runtime .zip when `entry.enclosed_name()` returns None — the zip crate rejects names that are absolute, contain drive labels (C:\\), or have `..` components, i.e. a zip-slip attempt (or a pathologically weird archive the tool refuses on principle). The extractor iterates entries manually instead of using archive.extract precisely to enforce this.","triggerScenarios":"A zip entry named `../evil.dll`, `C:\\Windows\\system32\\x.dll`, or `/abs/path`; archives built with non-normalized paths (`a/../b`); an upstream release accidentally packaged with absolute paths.","commonSituations":"Practically unreachable in normal use because the archive was SHA-256-verified against the pinned release first — a hit means the pinned release itself is malicious or mis-packaged, or the cache was corrupted post-verification.","solutions":["Do not extract the zip by hand or with other tools — keep it quarantined.","List entries to confirm: `unzip -l <archive>` and look for absolute/`..` names.","Clear the cache and retry once; if reproducible, report to the deno/laufey maintainers with the archive name."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"# Abort and report; never retry with permissive tools\nif deno desktop main.ts 2>&1 | grep -q \"refusing zip entry with unsafe path\"; then\n  echo \"SECURITY: zip-slip attempt in runtime archive — report upstream\" >&2; exit 2\nfi","preventionTips":["Rely on the tool's checksum-then-extract pipeline instead of side-loading archives into the cache.","Do not disable or patch out the enclosed_name check.","Report unsafe-path hits upstream with the entry name from the message."],"tags":["desktop","laufey","zip","zip-slip","path-traversal","security","windows"],"backgroundTag":"zip-slip-path-traversal","analyzedSha":"f7822238cab635a3a19f99f493f675fa81a7f9d8","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-31T04:17:50.494Z"}