CVE-2026-40180
Description
Quarkus OpenAPI Generator is Quarkus' extensions for generation of Rest Clients and server stubs generation. Prior to 2.16.0 and 2.15.0-lts, the unzip() method in ApicurioCodegenWrapper.java extracts ZIP entries without validating that the resolved file path stays within the intended output directory. At line 101, the destination is constructed as new File(toOutputDir, entry.getName()) and the content is written immediately. A malicious ZIP archive containing entries with path traversal sequences (e.g., ../../malicious.java) would write files outside the target directory. This vulnerability is fixed in 2.16.0 and 2.15.0-lts.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
io.quarkiverse.openapi.generator:quarkus-openapi-generatorMaven | < 2.16.0 | 2.16.0 |
Affected products
1Patches
2e2a9c629a3dfSolve Zip Slip Path Traversal in quarkus-openapi-generator ApicurioCodegenWrapper class (#1527)
2 files changed · +87 −1
server/deployment/src/main/java/io/quarkiverse/openapi/server/generator/deployment/codegen/apicurio/ApicurioCodegenWrapper.java+6 −1 modified@@ -93,12 +93,17 @@ public void generate(Path openApiResource) throws CodeGenException { } private void unzip(File fromZipFile, File toOutputDir) throws IOException, CodeGenException { + Path outputDirPath = toOutputDir.toPath().toAbsolutePath().normalize(); try (java.util.zip.ZipFile zipFile = new ZipFile(fromZipFile)) { Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); + Path entryPath = outputDirPath.resolve(entry.getName()).toAbsolutePath().normalize(); + if (!entryPath.startsWith(outputDirPath)) { + throw new IOException("Invalid ZIP entry: " + entry.getName()); + } + File entryDestination = entryPath.toFile(); assertGenerationSucceeded(zipFile, entry); - File entryDestination = new File(toOutputDir, entry.getName()); if (entry.isDirectory()) { entryDestination.mkdirs(); } else {
server/deployment/src/test/java/io/quarkiverse/openapi/server/generator/deployment/codegen/apicurio/ApicurioCodegenWrapperTest.java+81 −0 added@@ -0,0 +1,81 @@ +package io.quarkiverse.openapi.server.generator.deployment.codegen.apicurio; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ApicurioCodegenWrapperTest { + + @TempDir + Path tempDir; + + @Test + void shouldExtractRegularEntryInsideOutputDir() throws Exception { + Path zipPath = tempDir.resolve("safe.zip"); + createZip(zipPath, "nested/proof.txt", "ok"); + + Path outputDir = tempDir.resolve("out"); + Files.createDirectories(outputDir); + + invokeUnzip(zipPath.toFile(), outputDir.toFile()); + + Path extractedFile = outputDir.resolve("nested/proof.txt"); + assertTrue(Files.exists(extractedFile)); + assertEquals("ok", Files.readString(extractedFile)); + } + + @Test + void shouldRejectPathTraversalEntry() throws Exception { + Path zipPath = tempDir.resolve("traversal.zip"); + createZip(zipPath, "../../proof.txt", "pwned"); + + Path outputDir = tempDir.resolve("out"); + Files.createDirectories(outputDir); + + IOException error = assertThrows(IOException.class, () -> invokeUnzip(zipPath.toFile(), outputDir.toFile())); + assertTrue(error.getMessage().contains("Invalid ZIP entry")); + } + + private static void createZip(Path zipPath, String entryName, String content) throws IOException { + try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(zipPath))) { + zipOutputStream.putNextEntry(new ZipEntry(entryName)); + zipOutputStream.write(content.getBytes()); + zipOutputStream.closeEntry(); + } + } + + private static void invokeUnzip(File zipFile, File outputDir) throws Exception { + Method unzipMethod = ApicurioCodegenWrapper.class.getDeclaredMethod("unzip", File.class, File.class); + unzipMethod.setAccessible(true); + + Config config = ConfigProviderResolver.instance().getBuilder().build(); + ApicurioCodegenWrapper wrapper = new ApicurioCodegenWrapper(config, outputDir); + + try { + unzipMethod.invoke(wrapper, zipFile, outputDir); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException ioException) { + throw ioException; + } + if (cause instanceof RuntimeException runtimeException) { + throw runtimeException; + } + throw new RuntimeException(cause); + } + } +}
08b406414ff3Solve Zip Slip Path Traversal in quarkus-openapi-generator ApicurioCodegenWrapper class (#1524)
2 files changed · +87 −1
server/deployment/src/main/java/io/quarkiverse/openapi/server/generator/deployment/codegen/apicurio/ApicurioCodegenWrapper.java+6 −1 modified@@ -93,12 +93,17 @@ public void generate(Path openApiResource) throws CodeGenException { } private void unzip(File fromZipFile, File toOutputDir) throws IOException, CodeGenException { + Path outputDirPath = toOutputDir.toPath().toAbsolutePath().normalize(); try (java.util.zip.ZipFile zipFile = new ZipFile(fromZipFile)) { Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); + Path entryPath = outputDirPath.resolve(entry.getName()).toAbsolutePath().normalize(); + if (!entryPath.startsWith(outputDirPath)) { + throw new IOException("Invalid ZIP entry: " + entry.getName()); + } + File entryDestination = entryPath.toFile(); assertGenerationSucceeded(zipFile, entry); - File entryDestination = new File(toOutputDir, entry.getName()); if (entry.isDirectory()) { entryDestination.mkdirs(); } else {
server/deployment/src/test/java/io/quarkiverse/openapi/server/generator/deployment/codegen/apicurio/ApicurioCodegenWrapperTest.java+81 −0 added@@ -0,0 +1,81 @@ +package io.quarkiverse.openapi.server.generator.deployment.codegen.apicurio; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ApicurioCodegenWrapperTest { + + @TempDir + Path tempDir; + + @Test + void shouldExtractRegularEntryInsideOutputDir() throws Exception { + Path zipPath = tempDir.resolve("safe.zip"); + createZip(zipPath, "nested/proof.txt", "ok"); + + Path outputDir = tempDir.resolve("out"); + Files.createDirectories(outputDir); + + invokeUnzip(zipPath.toFile(), outputDir.toFile()); + + Path extractedFile = outputDir.resolve("nested/proof.txt"); + assertTrue(Files.exists(extractedFile)); + assertEquals("ok", Files.readString(extractedFile)); + } + + @Test + void shouldRejectPathTraversalEntry() throws Exception { + Path zipPath = tempDir.resolve("traversal.zip"); + createZip(zipPath, "../../proof.txt", "pwned"); + + Path outputDir = tempDir.resolve("out"); + Files.createDirectories(outputDir); + + IOException error = assertThrows(IOException.class, () -> invokeUnzip(zipPath.toFile(), outputDir.toFile())); + assertTrue(error.getMessage().contains("Invalid ZIP entry")); + } + + private static void createZip(Path zipPath, String entryName, String content) throws IOException { + try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(zipPath))) { + zipOutputStream.putNextEntry(new ZipEntry(entryName)); + zipOutputStream.write(content.getBytes()); + zipOutputStream.closeEntry(); + } + } + + private static void invokeUnzip(File zipFile, File outputDir) throws Exception { + Method unzipMethod = ApicurioCodegenWrapper.class.getDeclaredMethod("unzip", File.class, File.class); + unzipMethod.setAccessible(true); + + Config config = ConfigProviderResolver.instance().getBuilder().build(); + ApicurioCodegenWrapper wrapper = new ApicurioCodegenWrapper(config, outputDir); + + try { + unzipMethod.invoke(wrapper, zipFile, outputDir); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException ioException) { + throw ioException; + } + if (cause instanceof RuntimeException runtimeException) { + throw runtimeException; + } + throw new RuntimeException(cause); + } + } +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-jx2w-vp7f-456qghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-40180ghsaADVISORY
- github.com/quarkiverse/quarkus-openapi-generator/commit/08b406414ff30ed192e86c7fa924e57565534ff0nvdWEB
- github.com/quarkiverse/quarkus-openapi-generator/commit/e2a9c629a3df719abc74569a3795c265fd0e1239nvdWEB
- github.com/quarkiverse/quarkus-openapi-generator/security/advisories/GHSA-jx2w-vp7f-456qnvdWEB
News mentions
0No linked articles in our index yet.