{"record":{"id":"977f821520e868a0","repo":"apache/hadoop","slug":"expanding-entry-getname-would-create-fil","errorCode":null,"errorMessage":"expanding \" + entry.getName() + \" would create file outside of \" + toDir","messagePattern":"expanding \" \\+ entry\\.getName\\(\\) \\+ \" would create file outside of \" \\+ toDir","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java","lineNumber":750,"sourceCode":"  /**\n   * Given a stream input it will unzip the it in the unzip directory.\n   * passed as the second parameter\n   * @param inputStream The zip file as input\n   * @param toDir The unzip directory where to unzip the zip file.\n   * @throws IOException an exception occurred\n   */\n  public static void unZip(InputStream inputStream, File toDir)\n      throws IOException {\n    try (ZipArchiveInputStream zip = new ZipArchiveInputStream(inputStream)) {\n      int numOfFailedLastModifiedSet = 0;\n      String targetDirPath = toDir.getCanonicalPath() + File.separator;\n      for(ZipArchiveEntry entry = zip.getNextZipEntry();\n          entry != null;\n          entry = zip.getNextZipEntry()) {\n        if (!entry.isDirectory()) {\n          File file = new File(toDir, entry.getName());\n          if (!file.getCanonicalPath().startsWith(targetDirPath)) {\n            throw new IOException(\"expanding \" + entry.getName()\n                + \" would create file outside of \" + toDir);\n          }\n          File parent = file.getParentFile();\n          if (!parent.mkdirs() &&\n              !parent.isDirectory()) {\n            throw new IOException(\"Mkdirs failed to create \" +\n                parent.getAbsolutePath());\n          }\n          try (OutputStream out = Files.newOutputStream(file.toPath())) {\n            IOUtils.copyBytes(zip, out, BUFFER_SIZE);\n          }\n          if (!file.setLastModified(entry.getTime())) {\n            numOfFailedLastModifiedSet++;\n          }\n          if (entry.getPlatform() == ZipArchiveEntry.PLATFORM_UNIX) {\n            Files.setPosixFilePermissions(file.toPath(), permissionsFromMode(entry.getUnixMode()));\n          }\n        }","sourceCodeStart":732,"sourceCodeEnd":768,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java#L732-L768","documentation":"unZip(InputStream, File toDir) throws IOException(\"expanding <entry> would create file outside of <toDir>\") when new File(toDir, entry.getName()).getCanonicalPath() does not start with toDir's canonical path plus File.separator. This is the Zip Slip defense: a archive entry containing '../' segments, an absolute path, or a symlink trick would otherwise write outside the extraction directory. Seeing it means the zip is malicious or corrupt and the guard correctly aborted extraction.","triggerScenarios":"unZip() on a crafted zip with entries like '../../etc/passwd' or absolute names like '/etc/cron.d/x'; corrupted archives whose entry names contain path separators at odd places; archives produced by tools emitting entries with leading '../' normalization hazards.","commonSituations":"Processing user-uploaded or third-party bundles (connectors, plugins, nightly artifacts); supply-chain style zip-slip attack payloads; regression after switching archive producers.","solutions":["Treat the archive as untrusted: reject it, log entry.getName(), and alert — do not try to 'fix' the entry by stripping '../' silently","Re-create the artifact with a trusted archiver (relative entry names, no absolute components) and redeploy","Pre-scan entries with ZipArchiveInputStream and reject any whose resolved path leaves the target dir before calling unZip","Extract into a dedicated, low-privilege directory so even a slipped write has minimal blast radius"],"exampleFix":"// before\nFileUtil.unZip(uploaded, toDir); // aborts: entry escapes toDir\n\n// after: validate before extracting\ntry (ZipArchiveInputStream z = new ZipArchiveInputStream(Files.newInputStream(uploaded.toPath()))) {\n  String root = toDir.getCanonicalPath() + File.separator;\n  for (ZipArchiveEntry e = z.getNextZipEntry(); e != null; e = z.getNextZipEntry()) {\n    if (!new File(toDir, e.getName()).getCanonicalPath().startsWith(root)) {\n      throw new IOException(\"Rejected unsafe entry \" + e.getName());\n    }\n  }\n}\nFileUtil.unZip(uploaded, toDir);","handlingStrategy":"try-catch","validationCode":"String root = toDir.getCanonicalPath() + File.separator;\ntry (ZipArchiveInputStream z = new ZipArchiveInputStream(input)) {\n  for (ZipArchiveEntry e = z.getNextZipEntry(); e != null; e = z.getNextZipEntry()) {\n    if (!new File(toDir, e.getName()).getCanonicalPath().startsWith(root)) {\n      throw new SecurityException(\"Zip slip entry: \" + e.getName());\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  FileUtil.unZip(in, toDir);\n} catch (IOException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"outside of\")) {\n    // malicious/corrupt archive: quarantine it, alert, do NOT sanitize automatically\n    quarantine(archiveFile);\n  } else throw e;\n}","preventionTips":["Only extract archives from trusted, checksum-verified sources","Extract into a dedicated scratch directory with least privilege","Pre-scan entry names before extraction so rejection is atomic, since unZip aborts midway"],"tags":["zip-slip","path-traversal","security","archive-extraction","unzip"],"backgroundTag":"zip-slip-path-traversal","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}