{"record":{"id":"e13b6a6e210ac849","repo":"GoogleContainerTools/jib","slug":"blocked-unzipping-files-outside-destination-ent","errorCode":null,"errorMessage":"Blocked unzipping files outside destination: ${entryName} from ${archive}","messagePattern":"Blocked unzipping files outside destination: (.+?) from (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"critical","filePath":"jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ZipUtil.java","lineNumber":80,"sourceCode":"      throws IOException {\n    if (enableReproducibleTimestamps\n        && Files.isDirectory(destination)\n        && destination.toFile().list().length != 0) {\n      throw new IllegalStateException(\n          \"Cannot enable reproducible timestamps. They can only be enabled when the target root doesn't exist or is an empty directory\");\n    }\n    String canonicalDestination = destination.toFile().getCanonicalPath();\n    List<ZipEntry> entries = new ArrayList<>();\n    try (InputStream fileIn = new BufferedInputStream(Files.newInputStream(archive));\n        ZipInputStream zipIn = new ZipInputStream(fileIn)) {\n      for (ZipEntry entry = zipIn.getNextEntry(); entry != null; entry = zipIn.getNextEntry()) {\n        entries.add(entry);\n        Path entryPath = destination.resolve(entry.getName());\n\n        String canonicalTarget = entryPath.toFile().getCanonicalPath();\n        if (!canonicalTarget.startsWith(canonicalDestination + File.separator)) {\n          String offender = entry.getName() + \" from \" + archive;\n          throw new IOException(\"Blocked unzipping files outside destination: \" + offender);\n        }\n\n        if (entry.isDirectory()) {\n          Files.createDirectories(entryPath);\n        } else {\n          if (entryPath.getParent() != null) {\n            Files.createDirectories(entryPath.getParent());\n          }\n          try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(entryPath))) {\n            ByteStreams.copy(zipIn, out);\n          }\n        }\n      }\n    }\n    preserveModificationTimes(destination, entries, enableReproducibleTimestamps);\n  }\n\n  /**","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/GoogleContainerTools/jib/blob/fb949e2676afbbd7dd7a1ef61e20251931325654/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ZipUtil.java#L62-L98","documentation":"ZipUtil.unzip guards against zip-slip path traversal: each entry's resolved canonical path must remain under the destination's canonical path. When an entry name escapes the destination (e.g. via ../ segments or absolute entry names), unzip throws IOException 'Blocked unzipping files outside destination: <entryName> from <archive>'.","triggerScenarios":"unzip processes a ZipEntry whose getName() contains '..' segments or an absolute path so that destination.resolve(entry.getName()).getCanonicalPath() does not start with canonicalDestination + File.separator.","commonSituations":"Extracting a maliciously or accidentally crafted archive whose entries contain '../../etc/passwd' style names; archives built on Windows with absolute entry names; build caches poisoned with tampered dependency archives.","solutions":["Inspect the offending archive: the entry name after 'Blocked unzipping files outside destination:' identifies the malicious entry — replace the archive with a trusted copy.","Re-download or rebuild the archive from a trusted source (verify checksums) since legitimate JARs should not contain traversal entries.","Sanitize entry names before creating archives you control, ensuring they are relative and contain no '..' segments."],"exampleFix":"// before (creating an archive)\nZipEntry entry = new ZipEntry(\"../outside.txt\");\n// after\nZipEntry entry = new ZipEntry(\"safe/relative/outside.txt\");","handlingStrategy":"try-catch","validationCode":"// Pre-scan archive entries for traversal before unzipping\ntry (ZipInputStream zis = new ZipInputStream(Files.newInputStream(archive))) {\n  ZipEntry e;\n  while ((e = zis.getNextEntry()) != null) {\n    Path resolved = destination.resolve(e.getName()).normalize();\n    if (!resolved.startsWith(destination.normalize())) {\n      throw new IOException(\"unsafe zip entry: \" + e.getName());\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  ZipUtil.unzip(archive, destination, enableRepro);\n} catch (IOException e) {\n  if (e.getMessage().startsWith(\"Blocked unzipping files outside destination\")) {\n    logger.error(\"Zip-slip blocked; the archive is untrusted: \" + e.getMessage());\n  } else {\n    throw e;\n  }\n}","preventionTips":["Treat this error as a sign of a malicious/corrupt archive — replace the source","Verify archive checksums/signatures before extraction","Never disable or bypass the zip-slip check"],"tags":["zip","security","path-traversal","java"],"backgroundTag":"path-traversal-blocked","analyzedSha":"fb949e2676afbbd7dd7a1ef61e20251931325654","analyzedAt":"2026-09-06T14:04:09.491Z","contentChangedAt":"2026-09-06T14:04:09.491Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}