{"record":{"id":"15d555b2c2a9e18a","repo":"apache/hadoop","slug":"expanding-would-create-file-outside-of","errorCode":null,"errorMessage":"expanding {} would create file outside of {}","messagePattern":"expanding (.+?) would create file outside of (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"critical","filePath":"hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/RunJar.java","lineNumber":141,"sourceCode":"   * @param unpackRegex the pattern to match jar entries against\n   *\n   * @throws IOException if an I/O error has occurred or toDir\n   * cannot be created and does not already exist\n   */\n  public static void unJar(InputStream inputStream, File toDir,\n                           Pattern unpackRegex)\n      throws IOException {\n    try (JarInputStream jar = new JarInputStream(inputStream)) {\n      int numOfFailedLastModifiedSet = 0;\n      String targetDirPath = toDir.getCanonicalPath() + File.separator;\n      for (JarEntry entry = jar.getNextJarEntry();\n           entry != null;\n           entry = jar.getNextJarEntry()) {\n        if (!entry.isDirectory() &&\n            unpackRegex.matcher(entry.getName()).matches()) {\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          ensureDirectory(file.getParentFile());\n          try (OutputStream out = Files.newOutputStream(file.toPath())) {\n            IOUtils.copyBytes(jar, out, BUFFER_SIZE);\n          }\n          if (!file.setLastModified(entry.getTime())) {\n            numOfFailedLastModifiedSet++;\n          }\n        }\n      }\n      if (numOfFailedLastModifiedSet > 0) {\n        LOG.warn(\"Could not set last modfied time for {} file(s)\",\n            numOfFailedLastModifiedSet);\n      }\n      // ZipInputStream does not need the end of the file. Let's read it out.\n      // This helps with an additional TeeInputStream on the input.\n      IOUtils.copyBytes(inputStream, new NullOutputStream(), BUFFER_SIZE);","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/RunJar.java#L123-L159","documentation":"RunJar.unjar unpacks a jar (this is the JarInputStream variant used on streams) into a target directory. For each matching entry it resolves the target file's canonical path and requires it to start with the canonical target-directory prefix; an entry such as '../../evil.sh' or any name resolving outside makes it throw IOException(\"expanding <entry> would create file outside of <dir>\"). This is the zip-slip defense against path traversal in archive entry names.","triggerScenarios":"Unpacking a crafted or malformed jar whose entries contain '../' path segments; jars produced by broken tooling that stored absolute or traversal entry names; symlinked layouts where an entry name resolves outside the unpack root.","commonSituations":"Running 'hadoop jar' or calling RunJar/HadoopUnjar on an untrusted artifact; CI-built jars from nonstandard archivers; archives accepted by laxer runtimes but rejected by Hadoop's canonical-path check.","solutions":["Treat the jar as hostile: do not unpack or run it; obtain it from a trusted source","Inspect entry names with jar tf or unzip -l to identify the offending traversal entries","Rebuild the jar with a standard tool (Maven/Gradle/jar) so entries are relative to the jar root","Keep a Hadoop version that includes the canonical-path zip-slip check rather than bypassing it"],"exampleFix":"# before\nunzip -l bad.jar   # shows entries like ../../../etc/cron.d/x\n\n# after: rebuild from a clean root with relative names\ncd src-root && jar cf fixed.jar com/","handlingStrategy":"validation","validationCode":"Path target = toDir.getCanonicalFile().toPath();\ntry (JarFile jar = new JarFile(jarFile)) {\n  Enumeration<JarEntry> es = jar.entries();\n  while (es.hasMoreElements()) {\n    Path resolved = target.resolve(es.nextElement().getName()).normalize();\n    if (!resolved.startsWith(target)) {\n      throw new IOException(\"unsafe entry (zip-slip) in \" + jarFile);\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"try { RunJar.unjar(jarFile, toDir, unpackRegex); } catch (IOException e) { if (e.getMessage() != null && e.getMessage().startsWith(\"expanding\")) { rejectArtifact(e.getMessage()); } else throw e; }","preventionTips":["Only unpack jars from trusted sources","Scan archive entry names for '../' in CI before deployment","Build jars with standard tools so entries are relative paths","Never catch-and-continue past this check — it is path-traversal protection"],"tags":["hadoop","java","security","zip-slip","path-traversal","jar"],"backgroundTag":"zip-slip-path-traversal","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}