-
Notifications
You must be signed in to change notification settings - Fork 6
Add security fix for unzip process #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a security fix for the zip extraction process to prevent path traversal (zip slip) attacks, changes the unzip method to return the count of extracted files instead of void, and bumps the library version to 10.0.alpha12.
Changes:
- Modified the
unzipmethod to usenormalize()instead oftoRealPath()for path validation, preventing path traversal attacks while avoiding issues with non-existent paths - Changed the
unzipmethod return type fromvoidtointto return the count of successfully extracted files - Added comprehensive test coverage for the security fix with multiple path traversal attack vectors
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| rlib-io/src/main/java/javasabr/rlib/io/util/FileUtils.java | Implements security fix for path traversal in unzip method and adds file count return value |
| rlib-io/src/test/java/javasabr/rlib/io/FileUtilsTest.java | Adds new test case with malicious zip entries to verify path traversal protection |
| build.gradle | Increments project version from 10.0.alpha11 to 10.0.alpha12 |
| README.md | Updates version reference in documentation from 10.0.alpha11 to 10.0.alpha12 |
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.
| Path normalizedDestination = destination.normalize(); | ||
| int count = 0; | ||
| try (var zin = new ZipInputStream(Files.newInputStream(zipFile))) { | ||
| for (var entry = zin.getNextEntry(); entry != null; entry = zin.getNextEntry()) { | ||
|
|
||
| String entryName = entry.getName(); | ||
| Path targetFile = destination | ||
| .resolve(entryName) | ||
| .toRealPath(LinkOption.NOFOLLOW_LINKS); | ||
|
|
||
| if (!targetFile.startsWith(destination)) { | ||
| .normalize(); | ||
| if (!targetFile.startsWith(normalizedDestination)) { | ||
| LOGGER.warning(entryName, "Unexpected entry name:[%s] which is outside"::formatted); | ||
| continue; | ||
| } | ||
|
|
||
| if (entry.isDirectory()) { | ||
| Files.createDirectories(targetFile); | ||
| } else { | ||
| Files.createDirectories(targetFile.getParent()); | ||
| Files.copy(zin, targetFile, StandardCopyOption.REPLACE_EXISTING); | ||
| count++; |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The zip-slip protection based on Path.normalize()+startsWith() does not protect against traversal via pre-existing symlinks inside the destination (e.g., destination contains a symlinked subdir pointing outside). To make this a robust security fix, validate against destination.toRealPath(NOFOLLOW_LINKS) and ensure the resolved target’s real parent path stays within the destination before writing.
| public static int unzip(Path destination, Path zipFile) { | ||
| if (!Files.exists(destination)) { | ||
| throw new IllegalArgumentException("The folder " + destination + " doesn't exist."); | ||
| } |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unzip() only checks Files.exists(destination), but if destination exists and is a file (not a directory) extraction will fail later with an IOException. Consider validating Files.isDirectory(destination) up front and throwing an IllegalArgumentException with a clear message.
| } | |
| } | |
| if (!Files.isDirectory(destination)) { | |
| throw new IllegalArgumentException("The path " + destination + " is not a directory."); | |
| } |
| assertThat(unpackedFiles).isEqualTo(3); | ||
| assertThat(outputDir | ||
| .resolve("fileA.txt")) | ||
| .exists(); | ||
| assertThat(outputDir | ||
| .resolve("dir_a") | ||
| .resolve("fileC.txt")) | ||
| .exists(); |
Copilot
AI
Feb 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test expects 3 unpacked files, which implies the entry "dir_a/../fileD.txt" is accepted and extracted, but it never asserts that fileD actually exists. Adding an assertion for outputDir.resolve("fileD.txt") (and/or ensuring the directory entry behavior) would make the test fully validate the intended unzip behavior.
|
|
|
Add security fix for unzip process