{"record":{"id":"bd8d529af636d01c","repo":"jenkinsci/jenkins","slug":"zip-zipfile-getpath-contains-illegal-file-nam","errorCode":null,"errorMessage":"Zip ${zipFile.getPath()} contains illegal file name that breaks out of the target directory: ${e.getName()}","messagePattern":"Zip (.+?) contains illegal file name that breaks out of the target directory: (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"critical","filePath":"core/src/main/java/hudson/FilePath.java","lineNumber":715,"sourceCode":"            // TODO why does this not simply use ZipInputStream?\n            IOUtils.copy(in, tmpFile);\n            unzip(dir, tmpFile);\n        }\n        finally {\n            Files.delete(Util.fileToPath(tmpFile));\n        }\n    }\n\n    private static void unzip(File dir, File zipFile) throws IOException {\n        dir = dir.getAbsoluteFile();    // without absolutization, getParentFile below seems to fail\n\n        try (ZipFile zip = new ZipFile(zipFile)) {\n            Enumeration<ZipEntry> entries = zip.getEntries();\n            while (entries.hasMoreElements()) {\n                ZipEntry e = entries.nextElement();\n                File f = new File(dir, e.getName());\n                if (!f.getCanonicalFile().toPath().startsWith(dir.getCanonicalPath())) {\n                    throw new IOException(\n                        \"Zip \" + zipFile.getPath() + \" contains illegal file name that breaks out of the target directory: \" + e.getName());\n                }\n                if (e.isDirectory()) {\n                    mkdirs(f);\n                } else {\n                    File p = f.getParentFile();\n                    if (p != null) {\n                        mkdirs(p);\n                    }\n                    try (InputStream input = zip.getInputStream(e)) {\n                        IOUtils.copy(input, f);\n                    }\n                    try {\n                        FilePath target = new FilePath(f);\n                        int mode = e.getUnixMode();\n                        if (mode != 0)    // Ant returns 0 if the archive doesn't record the access mode\n                            target.chmod(mode);\n                    } catch (InterruptedException | NoSuchFileException ex) {","sourceCodeStart":697,"sourceCodeEnd":733,"githubUrl":"https://github.com/jenkinsci/jenkins/blob/2e228ff40b14dbc8b14ffbc6edf0e4383cf744fc/core/src/main/java/hudson/FilePath.java#L697-L733","documentation":"Thrown by FilePath.unzip as a zip-slip guard: for each entry, Jenkins computes the target file's canonical path and requires it to start with the destination directory's canonical path. If an entry name like ../evil escapes the target, extraction aborts before writing outside the directory. This is a security control, not a convenience check.","triggerScenarios":"A zip/tar entry name contains '..' segments that resolve above the destination dir; an absolute entry path resolving outside dir; symbolic-link-heavy archive resolving outside; archive from an untrusted or compromised source.","commonSituations":"Unpacking a tool/archive downloaded from an untrusted URL via install/unpack; a vendor zip with malformed entry names; crafted archive in a supply-chain attack; archive produced by a tool that emits absolute paths.","solutions":["Do not unpack untrusted archives with FilePath.unzip; pre-validate or repackage with relative entry names.","Inspect the archive entry names with `unzip -l` and remove/normalize any '..' or absolute paths.","Repack the archive so all entries are relative and contained.","If the source is trusted but sloppy, normalize entry names in a repackaging step before install."],"exampleFix":"null","handlingStrategy":"validation","validationCode":"static boolean isSafeArchive(File zipFile) throws IOException {\n    Path base = zipFile.getParentFile().getCanonicalFile().toPath();\n    try (ZipFile zf = new ZipFile(zipFile)) {\n        Enumeration<? extends ZipEntry> en = zf.entries();\n        while (en.hasMoreElements()) {\n            Path target = new File(base.toFile(), en.nextElement().getName()).getCanonicalFile().toPath();\n            if (!target.startsWith(base)) return false;\n        }\n    }\n    return true;\n}","typeGuard":"null","tryCatchPattern":"try {\n    fp.unzipFrom(stream);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"breaks out of the target directory\")) {\n        // reject the archive — do not attempt to sanitize path traversal\n        throw new SecurityException(\"Refusing unsafe archive\", e);\n    }\n    throw e;\n}","preventionTips":["Never unpack archives from untrusted sources without pre-validation.","Repackage untrusted archives to normalize entry names before install.","Treat this exception as a security incident, inspect the offending entry name."],"tags":["security","zip-slip","path-traversal","unpack"],"backgroundTag":null,"analyzedSha":"2e228ff40b14dbc8b14ffbc6edf0e4383cf744fc","analyzedAt":"2026-08-14T07:07:15.274Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}