{"record":{"id":"07c4ee1adc95a370","repo":"GoogleContainerTools/jib","slug":"blocked-unzipping-files-outside-destination-off","errorCode":null,"errorMessage":"Blocked unzipping files outside destination: ${offender}","messagePattern":"Blocked unzipping files outside destination: (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"critical","filePath":"jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarExtractor.java","lineNumber":83,"sourceCode":"        && 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<TarArchiveEntry> entries = new ArrayList<>();\n    try (InputStream in = new BufferedInputStream(Files.newInputStream(source));\n        TarArchiveInputStream tarArchiveInputStream = new TarArchiveInputStream(in)) {\n      for (TarArchiveEntry entry = tarArchiveInputStream.getNextEntry();\n          entry != null;\n          entry = tarArchiveInputStream.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 \" + source;\n          throw new IOException(\"Blocked unzipping files outside destination: \" + offender);\n        }\n        if (entry.isDirectory()) {\n          Files.createDirectories(entryPath);\n        } else {\n          if (entryPath.getParent() != null) {\n            Files.createDirectories(entryPath.getParent());\n          }\n\n          if (entry.isSymbolicLink()) {\n            Files.createSymbolicLink(entryPath, Paths.get(entry.getLinkName()));\n          } else {\n            try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(entryPath))) {\n              ByteStreams.copy(tarArchiveInputStream, out);\n            }\n          }\n        }\n      }\n    }","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/GoogleContainerTools/jib/blob/fb949e2676afbbd7dd7a1ef61e20251931325654/jib-core/src/main/java/com/google/cloud/tools/jib/tar/TarExtractor.java#L65-L101","documentation":"TarExtractor blocks extracting a tar entry whose canonical resolved path falls outside the destination directory, a Zip-Slip/path-traversal defense. A maliciously crafted tar (entry names like ../../etc/passwd) must not write files outside the extraction root, so Jib throws an IOException naming the offender.","triggerScenarios":"Tar contains an entry whose name escapes destination after canonicalization (../ traversal, absolute paths, symlink tricks); canonicalTarget does not start with canonicalDestination + File.separator.","commonSituations":"Extracting untrusted or third-party tars/layers; tar built with entries containing '../'; relocated/reused layer tars from an unknown source.","solutions":["Inspect the tar entries (tar -tf) and rebuild the archive with relative, non-escaping entry names.","Only extract tars from trusted sources.","Verify entry.getName() contains no leading '/' or '..' segments before extraction.","Use the offender path in the message to locate and remove the malicious entry."],"exampleFix":"// before: tar entries like '../../etc/evil'\nTarArchiveOutputStream out = ...; out.putArchiveEntry(new TarArchiveEntry(\"../../etc/evil\"));\n// after\nout.putArchiveEntry(new TarArchiveEntry(\"app/etc/evil\")); // stays inside destination","handlingStrategy":"validation","validationCode":"// Pre-scan tar entries for traversal before extraction\ntry (TarArchiveInputStream in = new TarArchiveInputStream(new BufferedInputStream(Files.newInputStream(tar)))) {\n  TarArchiveEntry e;\n  while ((e = in.getNextTarEntry()) != null) {\n    if (e.getName().startsWith(\"/\") || e.getName().contains(\"..\")) throw new IOException(\"Unsafe entry: \" + e.getName());\n  }\n}","typeGuard":null,"tryCatchPattern":"try { TarExtractor.extract(tar, dest, false); } catch (IOException e) { if (e.getMessage().startsWith(\"Blocked unzipping\")) { logger.error(\"Rejected malicious tar: {}\", e.getMessage()); } else { throw e; } }","preventionTips":["Only extract tars from trusted sources.","Never build entries with '..' or absolute-path names.","Scan archives with security tooling (e.g. tar -tf) from untrusted parties.","Keep Jib updated for current Zip-Slip mitigations."],"tags":["security","path-traversal","tar","zip-slip"],"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-14T05:17:10.506Z"}