denoland/deno · critical
refusing zip entry with unsafe path: {}
Error message
refusing zip entry with unsafe path: {} What it means
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.
Source
Thrown at cli/tools/desktop.rs:2273
let mut perms = meta.permissions();
perms.set_mode(safe);
let _ = std::fs::set_permissions(&dest_path, perms);
}
}
}
} else if name.ends_with(".zip") {
let mut archive = zip::ZipArchive::new(std::io::Cursor::new(data))?;
// Iterate entries manually rather than `archive.extract(dest)`: that
// helper has the same shape as the tar `unpack` we deliberately
// avoided (no perm masking; no defence-in-depth against zip-slip
// beyond the crate's own checks). Treat the archive as untrusted.
for i in 0..archive.len() {
let mut entry = archive.by_index(i)?;
// `enclosed_name` rejects drive labels, absolute paths and `..`
// components. Anything that fails this check is a zip-slip attempt
// (or a legitimately weird archive we don't want to handle).
let Some(rel_path) = entry.enclosed_name() else {
bail!("refusing zip entry with unsafe path: {}", entry.name());
};
// Defence in depth — re-check the components ourselves.
if rel_path.components().any(|c| {
matches!(
c,
std::path::Component::ParentDir | std::path::Component::RootDir
)
}) {
bail!(
"refusing zip entry with traversal path: {}",
rel_path.display()
);
}
// Refuse symlinks: with prior entries already extracted, a
// symlink-then-write pair is the standard zip-slip-via-symlink
// escape, and LAUFEY Windows archives have no legitimate need for
// them.
if entry.is_symlink() {View on GitHub (pinned to f7822238ca)
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.
Defensive patterns
Strategy: try-catch
Try / catch
# Abort and report; never retry with permissive tools if deno desktop main.ts 2>&1 | grep -q "refusing zip entry with unsafe path"; then echo "SECURITY: zip-slip attempt in runtime archive — report upstream" >&2; exit 2 fi
Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- refusing zip entry with traversal path: {}
- refusing tar entry with traversal path: {}
- refusing symlink entry in laufey archive: {}
- refusing tar entry that would unpack outside dest: {}
- ${prefix}Linter plugin must be an object
AI-assisted analysis of denoland/deno@f7822238ca (2026-08-20).
Data as JSON: /api/errors/068c742e6dcc68a7.
Report an issue: GitHub.