{"record":{"id":"4f3e7f813446e5e8","repo":"HMCL-dev/HMCL","slug":"theme-pack-entry-contains-an-unsafe-segment-ent","errorCode":null,"errorMessage":"Theme-pack entry contains an unsafe segment: ${entryName}","messagePattern":"Theme-pack entry contains an unsafe segment: (.+?)","errorType":"validation","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java","lineNumber":1411,"sourceCode":"\n    /// Returns a normalized and safe theme-pack zip entry name.\n    private static String normalizeThemePackEntryName(String entryName) throws IOException {\n        Objects.requireNonNull(entryName);\n\n        String normalized = entryName.trim().replace('\\\\', '/');\n        if (normalized.endsWith(\"/\")) {\n            normalized = normalized.substring(0, normalized.length() - 1);\n        }\n        if (normalized.isEmpty()) {\n            throw new IOException(\"Theme-pack entry is empty\");\n        }\n        if (normalized.startsWith(\"/\") || normalized.matches(\"^[A-Za-z]:.*\")) {\n            throw new IOException(\"Theme-pack entry must be relative: \" + entryName);\n        }\n\n        for (String segment : normalized.split(\"/\")) {\n            if (segment.isEmpty() || \".\".equals(segment) || \"..\".equals(segment)) {\n                throw new IOException(\"Theme-pack entry contains an unsafe segment: \" + entryName);\n            }\n            for (int i = 0; i < segment.length(); i++) {\n                char ch = segment.charAt(i);\n                if (Character.isISOControl(ch) || ch == '\\0') {\n                    throw new IOException(\"Theme-pack entry contains a control character: \" + entryName);\n                }\n            }\n        }\n        return normalized;\n    }\n\n    /// Checks that a theme-pack zip entry belongs to the current file layout.\n    private static void checkSupportedThemePackEntry(String entryName) throws IOException {\n        if (!ThemePackExporter.MANIFEST_ENTRY.equals(entryName)\n                && !\"assets\".equals(entryName)\n                && !entryName.startsWith(\"assets/\")) {\n            throw new IOException(\"Unsupported theme-pack entry: \" + entryName);\n        }","sourceCodeStart":1393,"sourceCodeEnd":1429,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java#L1393-L1429","documentation":"Each slash-separated segment of a normalized theme-pack entry name must be a real path segment. Empty segments, '.', or '..' make the path ambiguous or escaping, so ThemePackManager throws this IOException. This prevents zip-slip attacks where '..' entries would extract outside the pack directory.","triggerScenarios":"Installing a zip containing entries like 'assets/../theme-pack.json', 'assets//bg.png', or './theme-pack.json'.","commonSituations":"Archives built by scripts that concatenate path components without normalizing; malicious or careless repacking introducing '..' traversal segments; tools emitting double slashes.","solutions":["Normalize the archive's entry names to remove '.', '..', and duplicate slashes, then repack.","Reject or fix the source pack — this error can indicate a malicious archive, so do not bypass it.","Use ThemePackExporter, which writes canonical entry names."],"exampleFix":"// before\nString name = \"assets/\" + userSubdir + \"/bg.png\"; // userSubdir may be \"..\"\n// after\nString name = packRoot.relativize(packRoot.resolve(\"assets\", userSubdir, \"bg.png\").normalize());\nif (name.startsWith(\"..\") ) throw new IllegalArgumentException(\"entry escapes pack root\");","handlingStrategy":"validation","validationCode":"for (var e : Collections.list(new ZipFile(pack).entries())) {\n    for (String seg : e.getName().replace('\\\\', '/').split(\"/\")) {\n        if (seg.isEmpty() || seg.equals(\".\") || seg.equals(\"..\")) throw new IllegalArgumentException(\"unsafe segment in \" + e.getName());\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    ThemePackManager.install(pack, dir);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"unsafe segment\")) {\n      LOG.warn(\"Rejected theme pack with traversal segments — treat as untrusted\");\n      ui.show(\"Theme pack rejected: unsafe entry paths.\");\n    } else throw e;\n}","preventionTips":["Treat '..'-containing packs as potentially malicious — never bypass the check.","Normalize paths (Path.normalize + relativize) before writing entries.","Only install packs from trusted sources."],"tags":["zip","security","path-traversal"],"backgroundTag":"path-traversal-blocked","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}