HMCL-dev/HMCL · error
Zip entry is trying to create a symlink outside of the…
Error message
Zip entry is trying to create a symlink outside of the destination directory:
What it means
For symlink entries, Unzipper resolves the link target relative to the entry's destination parent and checks it stays inside destDir; if the resolved target escapes the destination it throws an IOException (symlink-based path traversal defense).
Solutions
- Use archives from trusted sources and repack without out-of-tree symlinks
- Replace absolute symlinks with relative ones that stay inside the archive root
- Extract with the built-in containment check enabled (default) and review offending entries listed in the message
- Remove unneeded symlink entries from the package entirely
Example fix
// before // archive entry: usr -> /usr (absolute, escapes destination) // after // archive entry: usr/lib -> ../lib (relative, stays inside destDir)
Defensive patterns
Strategy: validation
Validate before calling
static boolean symlinkStaysInside(Path destDir, Path entryDest, String linkTarget) {
return entryDest.getParent().resolve(linkTarget).toAbsolutePath().normalize()
.startsWith(destDir.toAbsolutePath().normalize());
} Try / catch
try {
unzipper.unzip();
} catch (IOException e) {
if (e.getMessage().startsWith("Zip entry is trying to create a symlink outside")) {
// treat archive as untrusted; reject and report
} else throw e;
} Prevention
- Reject archives containing absolute or out-of-tree symlinks
- Use relative symlink targets confined to the archive root
- Extract from trusted sources only; verify checksums/signatures
- Never disable the symlink containment check for untrusted input
When it happens
Trigger: Extracting archives containing symlinks whose targets (absolute paths or many '../' segments) resolve outside the extraction root, e.g. link -> /etc or link -> ../../somewhere.
Common situations: Malicious archives designed to plant symlinks for later write-escape attacks; archives meant to be installed system-wide being unpacked into a game directory; mistakenly packaged absolute symlinks from build machines.
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 write outside of the destination…
- 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/e9fe511542514074.
Report an issue: GitHub.
Appendix: source
Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Unzipper.java:133
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;
try {
targetPath = Path.of(linkTarget);
} catch (InvalidPathException e) {
throw new IOException("Zip entry has an invalid symlink target: " + entry.getName(), e);
}
if (!destFile.getParent().resolve(targetPath).toAbsolutePath().normalize().startsWith(destDir)) {
throw new IOException("Zip entry is trying to create a symlink outside of the destination directory: " + entry.getName());
}
try {
Files.createSymbolicLink(destFile, targetPath);
} catch (FileAlreadyExistsException ignored) {
}
} else {
try (InputStream input = reader.getInputStream(entry)) {
Files.copy(input, destFile, copyOptions);
} catch (FileAlreadyExistsException e) {
if (replaceExistentFile)
throw e;
}
if (entry.getUnixMode() != 0 && OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS) {
Files.setPosixFilePermissions(destFile, FileUtils.parsePosixFilePermission(entry.getUnixMode()));
}
}View on GitHub (pinned to 24702dc5a0)