{"record":{"id":"c1bdd564d0f36521","repo":"mybatis/mybatis-3","slug":"bad-zip-entry","errorCode":null,"errorMessage":"Bad zip entry: {}","messagePattern":"Bad zip entry: (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/io/DefaultVFS.java","lineNumber":89,"sourceCode":"      } else {\n        List<String> children = new ArrayList<>();\n        try {\n          if (isJar(url)) {\n            // Some versions of JBoss VFS might give a JAR stream even if the resource\n            // referenced by the URL isn't actually a JAR\n            is = url.openStream();\n            try (JarInputStream jarInput = new JarInputStream(is)) {\n              if (log.isDebugEnabled()) {\n                log.debug(\"Listing \" + url);\n              }\n              Path destinationDir = Path.of(path);\n              for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null;) {\n                if (log.isDebugEnabled()) {\n                  log.debug(\"Jar entry: \" + entry.getName());\n                }\n                File entryFile = destinationDir.resolve(entry.getName()).toFile().getCanonicalFile();\n                if (!entryFile.getPath().startsWith(destinationDir.toFile().getCanonicalPath())) {\n                  throw new IOException(\"Bad zip entry: \" + entry.getName());\n                }\n                children.add(entry.getName());\n              }\n            }\n          } else {\n            /*\n             * Some servlet containers allow reading from directory resources like a text file, listing the child\n             * resources one per line. However, there is no way to differentiate between directory and file resources\n             * just by reading them. To work around that, as each line is read, try to look it up via the class loader\n             * as a child of the current resource. If any line fails then we assume the current resource is not a\n             * directory.\n             */\n            is = url.openStream();\n            List<String> lines = new ArrayList<>();\n            try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {\n              for (String line; (line = reader.readLine()) != null;) {\n                if (log.isDebugEnabled()) {\n                  log.debug(\"Reader entry: \" + line);","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/io/DefaultVFS.java#L71-L107","documentation":"DefaultVFS (used to scan for mapper classes/packages on the classpath) streams a jar resource and, for each entry, checks that the resolved canonical path stays inside the destination directory. An entry whose path escapes it (../ traversal, absolute path) throws 'Bad zip entry' — this is zip-slip protection against maliciously crafted jars, and it can also fire on unusual jar entry names.","triggerScenarios":"MyBatis VFS scans a package for mapper candidates (e.g. <package name=\"...\"/> or typeAliasesPackage/typeHandlersPackage scanning) and encounters a jar (including nested jars in fat jars / spring-boot executable jars) containing entries like '../../x' or '/etc/passwd' whose canonical resolution leaves the destination dir.","commonSituations":"Spring Boot fat jars where nested-jar URL protocols interact badly with Path.of()/canonicalization; third-party jars containing unusual entry names; containers/SCM jars; security scanners injecting crafted jars; older mybatis versions against modern packaging.","solutions":["Update MyBatis to the latest 3.5.x — VFS handling of nested/spring-boot jars has been improved repeatedly.","Register a custom VFS (e.g. SpringBootVFS via configuration.setVfsImpl(...)) instead of DefaultVFS when running from an executable jar.","Narrow the scanned packages so the offending jar is not scanned (avoid scanning classpath roots like classpath*:**).","If a specific jar is malformed, identify it from the logged 'Listing <url>' debug line and exclude/replace it."],"exampleFix":"// before (spring boot fat jar, DefaultVFS scanning fails)\nsqlSessionFactoryBean.setTypeAliasesPackage(\"com.example.domain\");\n\n// after\nsqlSessionFactoryBean.setVfs(SpringBootVFS.class);\nsqlSessionFactoryBean.setTypeAliasesPackage(\"com.example.domain\");","handlingStrategy":"fallback","validationCode":"// detect fat-jar protocol and pick a suitable VFS before session creation\nURL u = Thread.currentThread().getContextClassLoader().getResource(\"mappers\");\nif (u != null && (\"jar\".equals(u.getProtocol()) || u.toExternalForm().contains(\"nested\"))) {\n  targetConfiguration.setVfsImpl(org.mybatis.spring.boot.autoconfigure.SpringBootVFS.class);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["In Spring Boot executables, always set VFS to SpringBootVFS (mybatis-spring-boot-starter does this for you — use the starter).","Keep mybatis current; VFS jar handling improves across 3.5.x releases.","Scope package scanning tightly to your own mapper packages instead of broad roots."],"tags":["mybatis","vfs","zip-slip","spring-boot","security","classpath"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}