apache/pulsar · error · IOException

Invalid zip file. Aborting unpacking.

Error message

Invalid zip file. Aborting unpacking.

What it means

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.

Source

Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/nar/NarUnpacker.java:144

    /**
     * Unpacks the NAR to the specified directory.
     *
     * @param workingDirectory
     *            the root directory to which the NAR should be unpacked.
     * @throws IOException
     *             if the NAR could not be unpacked.
     */
    private static void unpack(final File nar, final File workingDirectory) throws IOException {
        Path workingDirectoryPath = workingDirectory.toPath().normalize();
        try (ZipFile zipFile = new ZipFile(nar)) {
            Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();
            while (zipEntries.hasMoreElements()) {
                ZipEntry zipEntry = zipEntries.nextElement();
                String name = zipEntry.getName();
                Path targetFilePath = workingDirectoryPath.resolve(name).normalize();
                if (!targetFilePath.startsWith(workingDirectoryPath)) {
                    log.error().attr("entry", name).log("Invalid zip file with entry");
                    throw new IOException("Invalid zip file. Aborting unpacking.");
                }
                File f = targetFilePath.toFile();
                if (zipEntry.isDirectory()) {
                    FileUtils.ensureDirectoryExistAndCanReadAndWrite(f);
                } else {
                    // The directory entry might appear after the file entry
                    FileUtils.ensureDirectoryExistAndCanReadAndWrite(f.getParentFile());
                    makeFile(zipFile.getInputStream(zipEntry), f);
                }
            }
        }
    }

    /**
     * Creates the specified file, whose contents will come from the <tt>InputStream</tt>.
     *
     * @param inputStream
     *            the contents of the file to create.

View on GitHub (pinned to 820761864e)

Solutions

  1. Inspect the offending NAR (the log names the bad entry) and remove/replace it with a NAR built from a trusted source.
  2. Rebuild the NAR with correct packaging (jar/zip entries must be relative and stay within the archive root).
  3. Verify checksum/signature of third-party NARs before placing them in the functions/offloaders directory.
  4. 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).

Example fix

// before (malformed packaging)
ZipEntry entry = new ZipEntry(new File("/etc/passwd").getAbsolutePath());
// after
ZipEntry entry = new ZipEntry("META-INF/manifest.xml"); // relative, stays inside archive root
Defensive patterns

Strategy: validation

Validate before calling

static boolean isZipSafe(File nar) throws IOException {
    Path root = nar.getAbsoluteFile().getParentFile().toPath().normalize();
    try (ZipFile zf = new ZipFile(nar)) {
        Enumeration<? extends ZipEntry> es = zf.entries();
        while (es.hasMoreElements()) {
            Path resolved = root.resolve(es.nextElement().getName()).normalize();
            if (!resolved.startsWith(root)) return false;
        }
    }
    return true;
}

Type guard

static boolean isSafeEntryName(String name) {
    return name != null && !name.startsWith("/")
        && !name.contains("..");
}

Try / catch

try {
    NarUnpacker.unpackNar(nar, dir);
} catch (IOException e) {
    if ("Invalid zip file. Aborting unpacking.".equals(e.getMessage())) {
        log.error("Rejected NAR with zip-slip entry; verify source/signature", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/a788d62725e99ee2. Report an issue: GitHub.