{"record":{"id":"1022880c9b7a27ad","repo":"elastic/elasticsearch","slug":"2","errorCode":"2","errorMessage":"This plugin was built with an older plugin structure. Contact the plugin author to remove the intermediate \"elasticsearch\" directory within the plugin zip.","messagePattern":"This plugin was built with an older plugin structure\\. Contact the plugin author to remove the intermediate \"elasticsearch\" directory within the plugin zip\\.","errorType":"exception","errorClass":"UserException","httpStatus":null,"severity":"error","filePath":"distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/cli/InstallPluginAction.java","lineNumber":759,"sourceCode":"            : (HttpURLConnection) checksumUrl.openConnection(this.proxy);\n        if (connection.getResponseCode() == 404) {\n            return null;\n        }\n        return checksumUrl;\n    }\n\n    private Path unzip(Path zip, Path pluginsDir) throws IOException, UserException {\n        // unzip plugin to a staging temp dir\n\n        final Path target = stagingDirectory(pluginsDir);\n        pathsToDeleteOnShutdown.add(target);\n\n        try (ZipInputStream zipInput = new ZipInputStream(Files.newInputStream(zip))) {\n            ZipEntry entry;\n            byte[] buffer = new byte[8192];\n            while ((entry = zipInput.getNextEntry()) != null) {\n                if (entry.getName().startsWith(\"elasticsearch/\")) {\n                    throw new UserException(\n                        PLUGIN_MALFORMED,\n                        \"This plugin was built with an older plugin structure.\"\n                            + \" Contact the plugin author to remove the intermediate \\\"elasticsearch\\\" directory within the plugin zip.\"\n                    );\n                }\n                Path targetFile = target.resolve(entry.getName());\n\n                // Using the entry name as a path can result in an entry outside of the plugin dir,\n                // either if the name starts with the root of the filesystem, or it is a relative\n                // entry like ../whatever. This check attempts to identify both cases by first\n                // normalizing the path (which removes foo/..) and ensuring the normalized entry\n                // is still rooted with the target plugin directory.\n                if (targetFile.normalize().startsWith(target) == false) {\n                    throw new UserException(\n                        PLUGIN_MALFORMED,\n                        \"Zip contains entry name '\" + entry.getName() + \"' resolving outside of plugin directory\"\n                    );\n                }","sourceCodeStart":741,"sourceCodeEnd":777,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/cli/InstallPluginAction.java#L741-L777","documentation":"Thrown by unzip when a ZipEntry name starts with the literal prefix `elasticsearch/`. This indicates the plugin was packaged using the pre-5.x layout that nested everything under an intermediate `elasticsearch` directory. Modern installers expect the plugin's contents at the archive root. Exit code PLUGIN_MALFORMED (2).","triggerScenarios":"Installing a plugin zip that someone assembled from an old Elasticsearch source tree; a third-party plugin that hasn't been repackaged for current ES versions.","commonSituations":"Legacy/unmaintained plugins; zips produced by old build tooling; archives copied from pre-5.x example trees.","solutions":["Contact the plugin author for a repackaged build (as the message says).","Repackage the zip yourself by removing the top-level `elasticsearch/` directory so entries sit at the root.","Use a maintained fork or the equivalent built-in module if one exists."],"exampleFix":"# repackage\nmkdir /tmp/p && unzip old-plugin.zip -d /tmp/p\ncd /tmp/p/elasticsearch && zip -r ../fixed-plugin.zip . && cd ..\nelasticsearch-plugin install file:///tmp/fixed-plugin.zip","handlingStrategy":"validation","validationCode":"try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zip))) {\n    ZipEntry e;\n    while ((e = zis.getNextEntry()) != null) {\n        if (e.getName().startsWith(\"elasticsearch/\")) {\n            throw new IllegalStateException(\"Plugin uses legacy layout with intermediate elasticsearch/ dir\");\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Before distributing, confirm your zip's entries sit at the archive root with no `elasticsearch/` prefix.","Modernize the plugin build to the current packaging layout (entries directly under the plugin root)."],"tags":["plugin-install","legacy-layout","zip","plugin-malformed","elasticsearch"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}