{"record":{"id":"9e0ac7411e2c3d01","repo":"MuntashirAkon/AppManager","slug":"zip-slip-vulnerability-detected-nexpected-dest-new-file-9e0ac7","errorCode":null,"errorMessage":"Zip slip vulnerability detected!\\nExpected dest: \" + new File(realDestPath, entry.getName()) + \"\\nActual path: \" + realFilePath","messagePattern":"Zip slip vulnerability detected!\\\\nExpected dest: \" \\+ new File\\(realDestPath, entry\\.getName\\(\\)\\) \\+ \"\\\\nActual path: \" \\+ realFilePath","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"critical","filePath":"app/src/main/java/io/github/muntashirakon/AppManager/backup/adb/AndroidBackupExtractor.java","lineNumber":98,"sourceCode":"                if (!filename.startsWith(relativeDirInAb)) {\n                    throw new IOException(\"Unsupported file in AB: \" + filename);\n                }\n                // Remove apps/{packageName}/ part\n                filename = filename.substring(relDirSize);\n                Path file;\n                if (entry.isDirectory()) {\n                    file = dest.createDirectoriesIfRequired(filename);\n                } else file = dest.createNewArbitraryFile(filename, null);\n                // Check if the given entry is a link.\n                if (entry.isSymbolicLink() && file.getFilePath() != null) {\n                    String linkName = entry.getLinkName();\n                    file.delete();\n                    file.createNewSymbolicLink(linkName);\n                } else {\n                    // Zip slip vulnerability might still be present\n                    String realFilePath = file.getRealFilePath();\n                    if (realDestPath != null && realFilePath != null && !realFilePath.startsWith(realDestPath)) {\n                        throw new IOException(\"Zip slip vulnerability detected!\" +\n                                \"\\nExpected dest: \" + new File(realDestPath, entry.getName()) +\n                                \"\\nActual path: \" + realFilePath);\n                    }\n                    if (!entry.isDirectory()) {\n                        try (OutputStream os = file.openOutputStream()) {\n                            IoUtils.copy(tis, os);\n                        }\n                    }\n                }\n\n                // Categorize and build TarArchiveEntry\n                int category = getCategory(filename);\n                if (category == CAT_UNK && filename.equals(Constants.BACKUP_MANIFEST_FILENAME)) {\n                    // Ignore manifest file\n                    continue;\n                }\n                TarArchiveEntry targetEntry = getTargetArchiveEntry(entry, filename);\n                List<TargetTarEntry> targetTarEntries = mCategoryTargetEntriesMap.get(category);","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/app/src/main/java/io/github/muntashirakon/AppManager/backup/adb/AndroidBackupExtractor.java#L80-L116","documentation":"A second, post-resolution zip-slip check in AndroidBackupExtractor: even when the entry name looked safe, the real filesystem path of the target (following symlinks) is compared against realDestPath. If the resolved path escapes the destination, extraction is aborted with an IOException.","triggerScenarios":"A TAR entry containing a symbolic link (or hard-to-spot path) whose resolved target lies outside the extraction directory — e.g. a symlink previously created pointing to /data, followed by an entry writing through it.","commonSituations":"Maliciously crafted backups using symlink chains to escape the destination; archives restored into directories that already contain attacker-influenced symlinks.","solutions":["Refuse the archive — it likely contains a malicious symlink chain","Extract into a fresh, empty destination directory with no pre-existing symlinks","Remove suspicious symlinks from the destination before re-running the restore","Regenerate the backup from a trusted source"],"exampleFix":"// before (destination reused across restores)\nPath dest = new Path(\"/data/local/tmp/restore\");\nextractor.extract();\n// after (clean destination each run)\nPath dest = new Path(\"/data/local/tmp/restore-\" + System.nanoTime());\ndest.mkdirsIfNotExists();\nextractor.extract();","handlingStrategy":"validation","validationCode":"// Ensure destination contains no symlinks before extraction\nFiles.walk(destDir)\n     .filter(p -> Files.isSymbolicLink(p))\n     .forEach(p -> { throw new RuntimeException(\"Symlink in dest: \" + p); });","typeGuard":null,"tryCatchPattern":"try {\n    extractor.extract();\n} catch (IOException e) {\n    if (e.getMessage().contains(\"Zip slip\")) {\n        Log.e(TAG, \"Symlink-based traversal attempt blocked\", e);\n        wipeAndRecreate(destDir);\n    } else throw e;\n}","preventionTips":["Extract into empty directories with no pre-existing symlinks","Never reuse a destination across untrusted restores","Treat any zip-slip exception as evidence of a malicious archive","Audit destinations in shared/multi-user storage"],"tags":["security","zip-slip","symlink"],"backgroundTag":"path-traversal-blocked","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}