Skip to content

fix: zip cross directory attack vulnerability#312

Open
msslulu wants to merge 2 commits into
opentiny:developfrom
msslulu:fix/unzip
Open

fix: zip cross directory attack vulnerability#312
msslulu wants to merge 2 commits into
opentiny:developfrom
msslulu:fix/unzip

Conversation

@msslulu
Copy link
Copy Markdown
Contributor

@msslulu msslulu commented May 15, 2026

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • Bug Fixes
    • Reinforced security measures in ZIP file extraction with improved path validation to mitigate potential vulnerabilities.
    • Added enhanced diagnostic logging during ZIP extraction operations to support better troubleshooting and system monitoring.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

Warning

Rate limit exceeded

@msslulu has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 35 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4443669f-dfc2-4d6b-be3a-ae7ae0f0bd87

📥 Commits

Reviewing files that changed from the base of the PR and between 2ef9238 and ce180aa.

📒 Files selected for processing (1)
  • base/src/main/java/com/tinyengine/it/common/utils/Utils.java

Walkthrough

This PR hardens ZIP extraction in Utils.processZipEntries by adding path canonicalization, normalization, and traversal validation. The method now derives a canonical safe base directory, normalizes each entry's target path, validates it does not escape the safe directory, and uses Files.createDirectories() for safer extraction.

Changes

ZIP Path Traversal Prevention

Layer / File(s) Summary
ZIP path traversal prevention with canonicalization and validation
base/src/main/java/com/tinyengine/it/common/utils/Utils.java
processZipEntries resolves tempDir to a canonical safeDir, normalizes each ZIP entry's target path, validates it remains under safeDir (throwing SecurityException on escape), and uses Files.createDirectories() with parent directory handling instead of direct new File() construction.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A rabbit hops through zips with care,
No ../ tricks hiding in there!
Paths canonized, traversal denied,
With safeDir standing guard on each side. 🐰🔒

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: zip cross directory attack vulnerability' directly and clearly describes the primary change in the pull request—addressing a security vulnerability in ZIP extraction handling by implementing path traversal protection.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@base/src/main/java/com/tinyengine/it/common/utils/Utils.java`:
- Around line 242-252: The code validates targetPath against safeDir but then
creates and uses new File(tempDir, zipEntry.getName()), which can bypass the
normalization/symlink check; update the extraction/read logic in Utils (the
unzip/unpack method) to use the already-validated targetPath for all filesystem
operations: when creating directories, extracting entries, and reading contents,
call Files.createDirectories(targetPath) /
Files.createDirectories(targetPath.getParent()), pass targetPath (or
targetPath.toFile() if extractFile/readFileContent require File) into
extractFile and readFileContent instead of newFile, and remove the new
File(tempDir, ...) usage so all IO goes through the validated Path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c56d314-e82e-4809-b1ee-8a219b9b2807

📥 Commits

Reviewing files that changed from the base of the PR and between b7bf798 and 2ef9238.

📒 Files selected for processing (1)
  • base/src/main/java/com/tinyengine/it/common/utils/Utils.java

Comment on lines 242 to 252
File newFile = new File(tempDir, zipEntry.getName());

if (zipEntry.isDirectory()) {
// 创建目录(同时确保父目录存在)
Files.createDirectories(targetPath);
fileInfoList.add(new FileInfo(newFile.getName(), "", true)); // 添加目录
} else {
// 确保父目录存在
Files.createDirectories(targetPath.getParent());
extractFile(zis, newFile); // 解压文件
fileInfoList.add(new FileInfo(newFile.getName(), readFileContent(newFile), false)); // 添加文件内容
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify path-validation path vs extraction path usage in processZipEntries.
rg -n 'Path targetPath = safeDir\.resolve\(entryName\)\.normalize\(\);|File newFile = new File\(tempDir, zipEntry\.getName\(\)\);|extractFile\(zis, newFile\);|readFileContent\(newFile\)' base/src/main/java/com/tinyengine/it/common/utils/Utils.java

Repository: opentiny/tiny-engine-backend-java

Length of output: 392


🏁 Script executed:

#!/bin/bash
# Get broader context around the issue area
sed -n '220,260p' base/src/main/java/com/tinyengine/it/common/utils/Utils.java

Repository: opentiny/tiny-engine-backend-java

Length of output: 1695


🏁 Script executed:

#!/bin/bash
# Find where safeDir is defined/initialized
rg -n 'safeDir\s*=' base/src/main/java/com/tinyengine/it/common/utils/Utils.java -B 2 -A 2

Repository: opentiny/tiny-engine-backend-java

Length of output: 386


🏁 Script executed:

#!/bin/bash
# Find the method signature to understand the full context
rg -n 'void processZipEntries|Path.*processZipEntries|private.*processZipEntries' base/src/main/java/com/tinyengine/it/common/utils/Utils.java -A 15

Repository: opentiny/tiny-engine-backend-java

Length of output: 878


🏁 Script executed:

#!/bin/bash
# Find extractFile and readFileContent method signatures
rg -n 'private.*extractFile\(|void extractFile\(|public.*extractFile\(' base/src/main/java/com/tinyengine/it/common/utils/Utils.java -A 3

Repository: opentiny/tiny-engine-backend-java

Length of output: 406


🏁 Script executed:

#!/bin/bash
# Find readFileContent method
rg -n 'private.*readFileContent\(|String readFileContent\(' base/src/main/java/com/tinyengine/it/common/utils/Utils.java -A 3

Repository: opentiny/tiny-engine-backend-java

Length of output: 289


Use the validated targetPath as the only extraction/read path.

You validate targetPath against safeDir with path normalization and a security check, but then extract/read via new File(tempDir, zipEntry.getName()). Since safeDir is created via toRealPath() (which resolves symlinks), while newFile uses the raw tempDir, these paths can diverge if tempDir is a symlink, allowing the validation to be bypassed.

Suggested patch
-            File newFile = new File(tempDir, zipEntry.getName());
+            File newFile = targetPath.toFile();

             if (zipEntry.isDirectory()) {
                 // 创建目录(同时确保父目录存在)
                 Files.createDirectories(targetPath);
                 fileInfoList.add(new FileInfo(newFile.getName(), "", true));  // 添加目录
             } else {
                 // 确保父目录存在
-                Files.createDirectories(targetPath.getParent());
+                Path parent = targetPath.getParent();
+                if (parent != null) {
+                    Files.createDirectories(parent);
+                }
                 extractFile(zis, newFile);  // 解压文件
                 fileInfoList.add(new FileInfo(newFile.getName(), readFileContent(newFile), false));  // 添加文件内容
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
File newFile = new File(tempDir, zipEntry.getName());
if (zipEntry.isDirectory()) {
// 创建目录(同时确保父目录存在)
Files.createDirectories(targetPath);
fileInfoList.add(new FileInfo(newFile.getName(), "", true)); // 添加目录
} else {
// 确保父目录存在
Files.createDirectories(targetPath.getParent());
extractFile(zis, newFile); // 解压文件
fileInfoList.add(new FileInfo(newFile.getName(), readFileContent(newFile), false)); // 添加文件内容
File newFile = targetPath.toFile();
if (zipEntry.isDirectory()) {
// 创建目录(同时确保父目录存在)
Files.createDirectories(targetPath);
fileInfoList.add(new FileInfo(newFile.getName(), "", true)); // 添加目录
} else {
// 确保父目录存在
Path parent = targetPath.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
extractFile(zis, newFile); // 解压文件
fileInfoList.add(new FileInfo(newFile.getName(), readFileContent(newFile), false)); // 添加文件内容
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/src/main/java/com/tinyengine/it/common/utils/Utils.java` around lines
242 - 252, The code validates targetPath against safeDir but then creates and
uses new File(tempDir, zipEntry.getName()), which can bypass the
normalization/symlink check; update the extraction/read logic in Utils (the
unzip/unpack method) to use the already-validated targetPath for all filesystem
operations: when creating directories, extracting entries, and reading contents,
call Files.createDirectories(targetPath) /
Files.createDirectories(targetPath.getParent()), pass targetPath (or
targetPath.toFile() if extractFile/readFileContent require File) into
extractFile and readFileContent instead of newFile, and remove the new
File(tempDir, ...) usage so all IO goes through the validated Path.

List<FileInfo> fileInfoList = new ArrayList<>();
ZipEntry zipEntry;

// 将 tempDir 转为规范路径(例如解析符号链接、父目录等)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

补上测试用例

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants