MuntashirAkon/AppManager · critical · IOException

Zip slip vulnerability detected! Expected dest: ${new File(r

Error message

Zip slip vulnerability detected!
Expected dest: ${new File(realDestPath, entry.getName())}
Actual path: ${realFilePath}

What it means

After writing regular (non-link) entries, extract re-verifies the resolved real path: file.getRealFilePath() must still be inside realDestPath. If a symlink created earlier in the same archive (or pre-existing on disk) redirects the path outside the destination, it throws IOException("Zip slip vulnerability detected!...") with the expected vs. actual path. This is the second, symlink-aware layer of the zip-slip defense beyond the early name check (error 417).

Source

Thrown at app/src/main/java/io/github/muntashirakon/AppManager/utils/TarUtils.java:195

                            continue;
                        }
                        String linkName = entry.getLinkName();
                        // There's no need to check if the linkName exists as it may be extracted
                        // after the link has been created
                        // Special check for /data/app
                        if (linkName.startsWith("/data/app/")) {
                            linkName = getAbsolutePathToDataApp(linkName, realDataAppPath);
                        }
                        file.delete();
                        if (!file.createNewSymbolicLink(linkName)) {
                            throw new IOException("Couldn't create symbolic link " + file + " pointing to " + linkName);
                        }
                        continue;  // links do not need permission fixes
                    } else {
                        // Zip slip vulnerability might still be present
                        String realFilePath = file.getRealFilePath();
                        if (realDestPath != null && realFilePath != null && !realFilePath.startsWith(realDestPath)) {
                            throw new IOException("Zip slip vulnerability detected!" +
                                    "\nExpected dest: " + new File(realDestPath, entry.getName()) +
                                    "\nActual path: " + realFilePath);
                        }
                        if (!entry.isDirectory()) {
                            try (OutputStream os = file.openOutputStream()) {
                                IoUtils.copy(tis, os);
                            }
                        }
                    }
                    // Fix permissions
                    TarArchiveEntry finalEntry = entry;
                    ExUtils.exceptionAsIgnored(() -> Paths.setPermissions(file, finalEntry.getMode(),
                            finalEntry.getUserId(), finalEntry.getGroupId()));
                    // Restore timestamp
                    long modificationTime = entry.getModTime().getTime();
                    if (modificationTime > 0) { // Backward-compatibility
                        file.setLastModified(entry.getModTime().getTime());
                    }

View on GitHub (pinned to 0152f468fc)

Solutions

  1. Reject archives containing symlink entries whose targets resolve outside the destination, before extraction
  2. Extract into a fresh, empty destination directory to avoid pre-existing symlink traps
  3. Treat this exception as evidence of a malicious archive: delete all partially extracted files and quarantine the input
  4. Catch IOException and detect the 'Zip slip' message to apply security-specific handling

Example fix

// before
TarUtils.extract(tarIn, destDir, ...);
// after
try {
    TarUtils.extract(tarIn, destDir, ...);
} catch (IOException e) {
    if (e.getMessage().contains("Zip slip")) {
        FileUtils.deleteDir(destDir);
        throw new SecurityException("Malicious archive blocked: " + e.getMessage(), e);
    }
    throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Reject symlink entries whose targets escape the destination before extraction
if (entry.isSymbolicLink() && !new File(destDir, linkTarget).getCanonicalPath().startsWith(destDir.getCanonicalPath())) {
    throw new SecurityException("Unsafe symlink: " + linkTarget);
}

Type guard

boolean symlinkStaysInDir(File dest, String linkTarget) throws IOException {
    return new File(dest, linkTarget).getCanonicalPath().startsWith(dest.getCanonicalPath() + File.separator);
}

Try / catch

try {
    TarUtils.extract(in, dest, filters);
} catch (IOException e) {
    if (e.getMessage().contains("Zip slip")) {
        FileUtils.deleteDir(dest);
        throw new SecurityException("Malicious archive blocked: " + e.getMessage(), e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Extracting an archive where a symlink entry established a path leading outside the destination and a subsequent entry resolves through it; archives containing symlink-to-parent chains like 'link -> .' plus 'link/../escape'.

Common situations: Maliciously crafted archives designed to escape the extraction directory via links; archives that passed the naive name check (no '..' in entry names) but exploit filesystem-level resolution; re-extracting into a directory containing stale symlinks.

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


AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/cfe9028fa1bd447b. Report an issue: GitHub.