{"record":{"id":"794fdb27229997c8","repo":"apache/flink","slug":"illegal-escape-from-target-directory","errorCode":null,"errorMessage":"Illegal escape from target directory","messagePattern":"Illegal escape from target directory","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"flink-core/src/main/java/org/apache/flink/util/FileUtils.java","lineNumber":537,"sourceCode":"     * @throws IOException if file open fails or in case of unsafe access outside target directory\n     */\n    public static Path expandDirectory(Path file, Path targetDirectory) throws IOException {\n        FileSystem sourceFs = file.getFileSystem();\n        FileSystem targetFs = targetDirectory.getFileSystem();\n        Path rootDir = null;\n        try (ZipInputStream zis = new ZipInputStream(sourceFs.open(file))) {\n            ZipEntry entry;\n            String targetDirStr = targetDirectory.toString();\n            while ((entry = zis.getNextEntry()) != null) {\n                Path relativePath = new Path(entry.getName());\n                if (rootDir == null) {\n                    // the first entry contains the name of the original directory that was zipped\n                    rootDir = relativePath;\n                }\n\n                Path newFile = new Path(targetDirectory, relativePath);\n                if (!newFile.toString().startsWith(targetDirStr)) {\n                    throw new IOException(\"Illegal escape from target directory\");\n                }\n\n                if (entry.isDirectory()) {\n                    targetFs.mkdirs(newFile);\n                } else {\n                    try (FSDataOutputStream fileStream =\n                            targetFs.create(newFile, FileSystem.WriteMode.NO_OVERWRITE)) {\n                        // do not close the streams here as it prevents access to further zip\n                        // entries\n                        IOUtils.copyBytes(zis, fileStream, false);\n                    }\n                }\n                zis.closeEntry();\n            }\n        }\n        return new Path(targetDirectory, rootDir);\n    }\n","sourceCodeStart":519,"sourceCodeEnd":555,"githubUrl":"https://github.com/apache/flink/blob/2f3c205e9266cb30240eb7f4fdab15cad629a70f/flink-core/src/main/java/org/apache/flink/util/FileUtils.java#L519-L555","documentation":"FileUtils (unzip/expand of an archive into a target directory) is a Zip-Slip guard: for each ZipEntry it builds targetDirectory/entryName and verifies the result still starts with the target directory's path string. If an entry name like '../../etc/passwd' or an absolute path resolves outside the target, it throws IOException(\"Illegal escape from target directory\") to prevent the archive from overwriting arbitrary files.","triggerScenarios":"Unzipping a maliciously crafted or corrupted archive whose entry names contain '../' segments, rooted/absolute paths, or drive letters that make new Path(targetDirectory, relativePath) escape targetDirStr; can also fire on benign-but-odd archives where the string startsWith check fails for the normalized path.","commonSituations":"Deploying user-supplied job JARs/bundles or community distribution archives; downloading a partially-corrupted zip; a zip created by a tool that emits entries with leading '/' or Windows-style components.","solutions":["Inspect the archive with `unzip -l` or `jar tf` and look for entries starting with '/', '..' or drive letters; reject or re-create the archive","Re-package the zip from a trusted source so all entry names are relative paths under the intended root","If you control the producer, fix it to write entries with normalized relative names (no leading slash, no '..' segments)"],"exampleFix":"// producing safe entries when zipping\n// before\nzos.putNextEntry(new ZipEntry(\"/abs/or/../escaping/name\"));\n// after\nzos.putNextEntry(new ZipEntry(\"rel/dir/name\"));","handlingStrategy":"validation","validationCode":"static void assertSafeEntries(Path zip) throws IOException {\n    try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zip))) {\n        for (ZipEntry e; (e = zis.getNextEntry()) != null; ) {\n            String n = e.getName();\n            if (n.startsWith(\"/\") || n.contains(\"..\")) throw new IOException(\"Unsafe entry: \" + n);\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"try { FileUtils.unzip(...) } catch (IOException e) { if (e.getMessage().contains(\"Illegal escape\")) rejectArchive(); else throw e; }","preventionTips":["Only extract archives from trusted, checksum-verified sources","Scan entry names for '..', leading '/', and drive letters before extraction","Extract into a dedicated, low-privilege directory so even an escape is contained"],"tags":["security","zip-slip","filesystem","archive"],"backgroundTag":null,"analyzedSha":"2f3c205e9266cb30240eb7f4fdab15cad629a70f","analyzedAt":"2026-08-14T08:48:24.518Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}