{"record":{"id":"a788d62725e99ee2","repo":"apache/pulsar","slug":"invalid-zip-file-aborting-unpacking","errorCode":null,"errorMessage":"Invalid zip file. Aborting unpacking.","messagePattern":"Invalid zip file\\. Aborting unpacking\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"pulsar-common/src/main/java/org/apache/pulsar/common/nar/NarUnpacker.java","lineNumber":144,"sourceCode":"    /**\n     * Unpacks the NAR to the specified directory.\n     *\n     * @param workingDirectory\n     *            the root directory to which the NAR should be unpacked.\n     * @throws IOException\n     *             if the NAR could not be unpacked.\n     */\n    private static void unpack(final File nar, final File workingDirectory) throws IOException {\n        Path workingDirectoryPath = workingDirectory.toPath().normalize();\n        try (ZipFile zipFile = new ZipFile(nar)) {\n            Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();\n            while (zipEntries.hasMoreElements()) {\n                ZipEntry zipEntry = zipEntries.nextElement();\n                String name = zipEntry.getName();\n                Path targetFilePath = workingDirectoryPath.resolve(name).normalize();\n                if (!targetFilePath.startsWith(workingDirectoryPath)) {\n                    log.error().attr(\"entry\", name).log(\"Invalid zip file with entry\");\n                    throw new IOException(\"Invalid zip file. Aborting unpacking.\");\n                }\n                File f = targetFilePath.toFile();\n                if (zipEntry.isDirectory()) {\n                    FileUtils.ensureDirectoryExistAndCanReadAndWrite(f);\n                } else {\n                    // The directory entry might appear after the file entry\n                    FileUtils.ensureDirectoryExistAndCanReadAndWrite(f.getParentFile());\n                    makeFile(zipFile.getInputStream(zipEntry), f);\n                }\n            }\n        }\n    }\n\n    /**\n     * Creates the specified file, whose contents will come from the <tt>InputStream</tt>.\n     *\n     * @param inputStream\n     *            the contents of the file to create.","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-common/src/main/java/org/apache/pulsar/common/nar/NarUnpacker.java#L126-L162","documentation":"unpack() guards against Zip Slip: every zip entry path is resolved and normalized, and if the resulting path escapes the NAR's working directory the unpack is aborted with this IOException. This protects the classloader from a malicious NAR whose entries contain '../' or absolute paths that would write outside the extraction directory.","triggerScenarios":"Loading a NAR via NarUnpacker.unpackNar(...) where the archive contains an entry whose name resolves outside the target directory, e.g. '../evil.so' or an absolute entry path produced by a tampered or incorrectly built zip/jar.","commonSituations":"Using a NAR downloaded from an untrusted source or built by a buggy packaging script; a corrupted/truncated download whose central directory was patched; supply-chain attack attempts.","solutions":["Inspect the offending NAR (the log names the bad entry) and remove/replace it with a NAR built from a trusted source.","Rebuild the NAR with correct packaging (jar/zip entries must be relative and stay within the archive root).","Verify checksum/signature of third-party NARs before placing them in the functions/offloaders directory.","If you own the zip-creation code, ensure entry names never contain '..' segments or leading '/' (e.g. don't pass absolute File paths to ZipEntry)."],"exampleFix":"// before (malformed packaging)\nZipEntry entry = new ZipEntry(new File(\"/etc/passwd\").getAbsolutePath());\n// after\nZipEntry entry = new ZipEntry(\"META-INF/manifest.xml\"); // relative, stays inside archive root","handlingStrategy":"validation","validationCode":"static boolean isZipSafe(File nar) throws IOException {\n    Path root = nar.getAbsoluteFile().getParentFile().toPath().normalize();\n    try (ZipFile zf = new ZipFile(nar)) {\n        Enumeration<? extends ZipEntry> es = zf.entries();\n        while (es.hasMoreElements()) {\n            Path resolved = root.resolve(es.nextElement().getName()).normalize();\n            if (!resolved.startsWith(root)) return false;\n        }\n    }\n    return true;\n}","typeGuard":"static boolean isSafeEntryName(String name) {\n    return name != null && !name.startsWith(\"/\")\n        && !name.contains(\"..\");\n}","tryCatchPattern":"try {\n    NarUnpacker.unpackNar(nar, dir);\n} catch (IOException e) {\n    if (\"Invalid zip file. Aborting unpacking.\".equals(e.getMessage())) {\n        log.error(\"Rejected NAR with zip-slip entry; verify source/signature\", e);\n    }\n    throw e;\n}","preventionTips":["Only load NARs from trusted, checksum-verified sources.","Verify NAR signatures before placing them in the functions/offloaders directory.","Build NARs with standard jar/zip tooling using relative entry names.","Treat this exception as a security event and quarantine the offending archive."],"tags":["security","zip-slip","nar"],"backgroundTag":"zip-slip-path-traversal","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}