HMCL-dev/HMCL · error
Zip entry is trying to write outside of the destination…
Error message
Zip entry is trying to write outside of the destination directory:
What it means
Unzipper rejects zip entries whose normalized target path escapes destDir (zip-slip attack). It resolves the entry against the destination and verifies the result still starts with destDir; if not, an IOException naming the entry is thrown.
Solutions
- Obtain archives from trusted, verified sources and re-download a clean copy
- Inspect the archive (jar tf / unzip -l) for '..' or absolute-path entries and rebuild it if needed
- Keep the default Unzipper safety behavior — do not bypass the destDir containment check
- Extract to a dedicated scratch directory and validate contents before moving them into place
Example fix
// before // archive contains entry: ../../../plugins/evil.jar new Unzipper().zip(zipFile).destDirectory(pluginsDir).unzip(); // after // rebuild the archive with relative paths only, then extract new Unzipper().zip(zipFileFixed).destDirectory(pluginsDir).unzip();
Defensive patterns
Strategy: validation
Validate before calling
static boolean isInside(Path root, Path candidate) {
return candidate.toAbsolutePath().normalize().startsWith(root.toAbsolutePath().normalize());
}
// pre-scan archive:
try (ZipFile zf = new ZipFile(zip)) {
for (ZipEntry e : Collections.list(zf.entries()))
if (e.getName().contains("..") || Paths.get(e.getName()).isAbsolute())
throw new IOException("unsafe entry: " + e.getName());
} Type guard
static boolean safeEntryName(String name) {
Path p = Paths.get(name);
return !p.isAbsolute() && !p.normalize().startsWith("..");
} Try / catch
try {
new Unzipper().zip(zip).destDirectory(dest).unzip();
} catch (IOException e) {
if (e.getMessage().startsWith("Zip entry is trying to write outside")) {
// reject the archive / warn user about unsafe content
} else throw e;
} Prevention
- Only extract archives from trusted, checksum-verified sources
- Pre-scan entry names for '..' and absolute paths
- Extract untrusted archives into disposable scratch directories
- Keep Unzipper's path containment check enabled
When it happens
Trigger: Extracting a maliciously or accidentally crafted archive containing entries like '../../evil.so' or absolute paths that resolve outside the destination directory.
Common situations: Installing mods/resource packs from untrusted third-party sites; archives built on Windows with backslash paths or '..' segments; corrupted archives from interrupted downloads.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Zip entry is trying to create a symlink outside of the…
- Theme pack does not contain
- Theme-pack entry contains an unsafe segment
- Theme-pack entry contains a control character
- path escapes instance root
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/193616422d8e7b76.
Report an issue: GitHub.
Appendix: source
Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Unzipper.java:107
CopyOption[] copyOptions = replaceExistentFile
? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING}
: new CopyOption[]{};
long entryCount = 0L;
try (ZipArchiveReader reader = CompressingUtils.openZipFileWithPossibleEncoding(zipFile, encoding)) {
String pathPrefix = StringUtils.addSuffix(subDirectory, "/");
for (ZipArchiveEntry entry : reader.getEntries()) {
String normalizedPath = FileUtils.normalizePath(entry.getName());
if (!normalizedPath.startsWith(pathPrefix)) {
continue;
}
String relativePath = normalizedPath.substring(pathPrefix.length());
Path destFile = destDir.resolve(relativePath).toAbsolutePath().normalize();
if (!destFile.startsWith(destDir)) {
throw new IOException("Zip entry is trying to write outside of the destination directory: " + entry.getName());
}
if (filter != null && !filter.accept(entry, destFile, relativePath)) {
continue;
}
entryCount++;
if (entry.isDirectory()) {
Files.createDirectories(destFile);
} else {
Files.createDirectories(destFile.getParent());
if (entry.isUnixSymlink()) {
String linkTarget = reader.getUnixSymlink(entry);
if (replaceExistentFile)
Files.deleteIfExists(destFile);
Path targetPath;View on GitHub (pinned to 24702dc5a0)