{"record":{"id":"e9fe511542514074","repo":"HMCL-dev/HMCL","slug":"zip-entry-is-trying-to-create-a-symlink-outside-of","errorCode":null,"errorMessage":"Zip entry is trying to create a symlink outside of the destination directory: ","messagePattern":"Zip entry is trying to create a symlink outside of the destination directory: ","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Unzipper.java","lineNumber":133,"sourceCode":"\n                if (entry.isDirectory()) {\n                    Files.createDirectories(destFile);\n                } else {\n                    Files.createDirectories(destFile.getParent());\n                    if (entry.isUnixSymlink()) {\n                        String linkTarget = reader.getUnixSymlink(entry);\n                        if (replaceExistentFile)\n                            Files.deleteIfExists(destFile);\n\n                        Path targetPath;\n                        try {\n                            targetPath = Path.of(linkTarget);\n                        } catch (InvalidPathException e) {\n                            throw new IOException(\"Zip entry has an invalid symlink target: \" + entry.getName(), e);\n                        }\n\n                        if (!destFile.getParent().resolve(targetPath).toAbsolutePath().normalize().startsWith(destDir)) {\n                            throw new IOException(\"Zip entry is trying to create a symlink outside of the destination directory: \" + entry.getName());\n                        }\n\n                        try {\n                            Files.createSymbolicLink(destFile, targetPath);\n                        } catch (FileAlreadyExistsException ignored) {\n                        }\n                    } else {\n                        try (InputStream input = reader.getInputStream(entry)) {\n                            Files.copy(input, destFile, copyOptions);\n                        } catch (FileAlreadyExistsException e) {\n                            if (replaceExistentFile)\n                                throw e;\n                        }\n\n                        if (entry.getUnixMode() != 0 && OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS) {\n                            Files.setPosixFilePermissions(destFile, FileUtils.parsePosixFilePermission(entry.getUnixMode()));\n                        }\n                    }","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Unzipper.java#L115-L151","documentation":"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).","triggerScenarios":"Extracting archives containing symlinks whose targets (absolute paths or many '../' segments) resolve outside the extraction root, e.g. link -> /etc or link -> ../../somewhere.","commonSituations":"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.","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"],"exampleFix":"// before\n// archive entry: usr -> /usr  (absolute, escapes destination)\n// after\n// archive entry: usr/lib -> ../lib  (relative, stays inside destDir)","handlingStrategy":"validation","validationCode":"static boolean symlinkStaysInside(Path destDir, Path entryDest, String linkTarget) {\n    return entryDest.getParent().resolve(linkTarget).toAbsolutePath().normalize()\n        .startsWith(destDir.toAbsolutePath().normalize());\n}","typeGuard":null,"tryCatchPattern":"try {\n    unzipper.unzip();\n} catch (IOException e) {\n    if (e.getMessage().startsWith(\"Zip entry is trying to create a symlink outside\")) {\n        // treat archive as untrusted; reject and report\n    } else throw e;\n}","preventionTips":["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"],"tags":["io","zip","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}