MuntashirAkon/AppManager · error · IOException

Zip slip vulnerability detected!\nExpected dest: " + new Fil

Error message

Zip slip vulnerability detected!\nExpected dest: " + new File(realDestPath, entry.getName()) + "\nActual path: " + (filename != null ? new File(realDestPath, filename) : realDestPath)

What it means

IOException thrown by AndroidBackupExtractor while parsing an Android 'ab' backup TAR stream: an entry whose normalized path is null or starts with '../' would extract outside the destination directory, so the extractor aborts before creating any file. This is the early zip-slip defense against path traversal in malicious backup archives.

Source

Thrown at app/src/main/java/io/github/muntashirakon/AppManager/backup/adb/AndroidBackupExtractor.java:76

    public AndroidBackupExtractor(@NonNull Path abFile, @NonNull Path temporaryDir, @NonNull String packageName) throws IOException {
        mWorkingDir = temporaryDir;
        String relativeDirInAb = Constants.APPS_PREFIX + packageName + File.separator;
        String abFilename = Paths.trimPathExtension(abFile.getName());
        Path tarFile = temporaryDir.createNewFile(abFilename + ".tar", null);
        mFilesToBeDeleted.add(tarFile);
        Path dest = temporaryDir.createNewDirectory(abFilename);
        mFilesToBeDeleted.add(dest);
        toTar(abFile, tarFile, null);
        try (InputStream fis = tarFile.openInputStream();
             TarArchiveInputStream tis = new TarArchiveInputStream(fis)) {
            String realDestPath = dest.getRealFilePath();
            int relDirSize = relativeDirInAb.length();
            TarArchiveEntry entry;
            while ((entry = tis.getNextTarEntry()) != null) {
                String filename = Paths.normalize(entry.getName());
                // Early zip slip vulnerability check to avoid creating any files at all
                if (filename == null || filename.startsWith("../")) {
                    throw new IOException("Zip slip vulnerability detected!" +
                            "\nExpected dest: " + new File(realDestPath, entry.getName()) +
                            "\nActual path: " + (filename != null ? new File(realDestPath, filename) : realDestPath));
                }
                if (!filename.startsWith(relativeDirInAb)) {
                    throw new IOException("Unsupported file in AB: " + filename);
                }
                // Remove apps/{packageName}/ part
                filename = filename.substring(relDirSize);
                Path file;
                if (entry.isDirectory()) {
                    file = dest.createDirectoriesIfRequired(filename);
                } else file = dest.createNewArbitraryFile(filename, null);
                // Check if the given entry is a link.
                if (entry.isSymbolicLink() && file.getFilePath() != null) {
                    String linkName = entry.getLinkName();
                    file.delete();
                    file.createNewSymbolicLink(linkName);
                } else {

View on GitHub (pinned to 0152f468fc)

Solutions

  1. Do not use the backup archive — it is malicious or corrupt; obtain a clean backup
  2. Re-create the backup with App Manager or adb backup so entry names are relative and sanitized
  3. Inspect archive entry names for '../' before extraction with a separate tool
  4. Ensure extraction destination is dedicated and readable, then re-run with a trusted archive

Example fix

// before (caller passes an untrusted .ab straight to extraction)
extractor.extract(new File("untrusted.ab"), destDir);
// after (pre-screen entry names)
for (String name : listTarEntryNames(backupFile)) {
    if (name == null || Paths.normalize(name) == null || Paths.normalize(name).startsWith("../")) {
        throw new IOException("Rejecting archive: unsafe entry " + name);
    }
}
extractor.extract(new File("untrusted.ab"), destDir);
Defensive patterns

Strategy: validation

Validate before calling

boolean isSafe(String entryName) {
    String norm = Paths.normalize(entryName);
    return norm != null && !norm.startsWith("../");
}

Type guard

// Java has no runtime type narrowing; use a predicate
java.util.function.Predicate<String> safeEntry =
    name -> Paths.normalize(name) != null && !Paths.normalize(name).startsWith("../");

Try / catch

try {
    extractor.extract();
} catch (IOException e) {
    if (e.getMessage().contains("Zip slip")) {
        Log.e(TAG, "Refusing malicious backup archive", e);
        deletePartialOutput(destDir);
    } else throw e;
}

Prevention

When it happens

Trigger: Feeding the extractor a crafted or corrupt .ab file where a TAR entry name contains '../' (or normalizes to nothing), causing new File(realDestPath, entry.getName()) to escape realDestPath.

Common situations: Restoring an untrusted/shared backup file; archives produced by third-party tools that don't sanitize entry names; tampered backups intended to overwrite files elsewhere on the filesystem.

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/f58436345c0c9107. Report an issue: GitHub.