HMCL-dev/HMCL · critical · IOException
Unsecure path:
Error message
Unsecure path:
What it means
Thrown while completing/updating a server modpack when a file path declared in the remote modpack manifest resolves outside the instance root directory. HMCL normalizes each manifest entry and requires it to stay under the instance root; otherwise the path is treated as a path-traversal (zip-slip style) attack and installation aborts. This protects against malicious manifests writing files anywhere on disk.
Solutions
- Inspect the modpack manifest (modpack.json / server.json) and fix or remove entries whose path contains '..' or is absolute.
- Re-download the modpack from a trusted source; the manifest may be corrupted or tampered with.
- If you are the pack author, regenerate the manifest so every path is relative and stays inside the instance directory.
- Report the pack to its maintainer if the path traversal looks intentional.
Example fix
// before (manifest entry)
{"path": "../../../evil.jar", ...}
// after
{"path": "mods/legit-mod.jar", ...} Defensive patterns
Strategy: validation
Validate before calling
Path rootPath = instanceRoot.toAbsolutePath().normalize();
for (var file : manifest.getFiles()) {
Path p = rootPath.resolve(file.getPath()).toAbsolutePath().normalize();
if (!p.startsWith(rootPath))
throw new IllegalArgumentException("Unsecure path: " + file.getPath());
} Try / catch
try {
task.execute();
} catch (IOException e) {
if (e.getMessage().startsWith("Unsecure path:")) {
// reject/treat modpack as malicious or corrupted
}
} Prevention
- Only install server modpacks from trusted sources.
- Audit manifest paths for '..' segments or absolute paths before accepting a pack.
- Never hand-edit manifest path entries.
When it happens
Trigger: ServerModpackCompletionTask.execute() iterating remoteManifest.getFiles() encounters a FileInformation whose path contains '../' segments, an absolute path, or a symlink-resolved location that escapes rootPath after normalize().
Common situations: A maliciously or incorrectly crafted server modpack manifest with paths like '../../.minecraft/...'; manifests produced by broken exporter tools; editing manifest JSON by hand and introducing '..' segments.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Invalid library path:
- /assets/
- Interrupted while waiting for pending instance writes
- Instance
- Malformed modpack configuration
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/adfc56b56ca6598a.
Report an issue: GitHub.
Appendix: source
Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackCompletionTask.java:174
Path rootPath = instance.getInstanceRoot().toAbsolutePath().normalize();
Map<String, ModpackConfiguration.FileInformation> files = manifest.getManifest().getFiles().stream()
.collect(Collectors.toMap(ModpackConfiguration.FileInformation::getPath,
Function.identity()));
Set<String> remoteFiles = remoteManifest.getFiles().stream().map(ModpackConfiguration.FileInformation::getPath)
.collect(Collectors.toSet());
Path runDirectory = instance.getRunDirectory().toAbsolutePath().normalize();
Path modsDirectory = runDirectory.resolve("mods");
int total = 0;
// for files in new modpack
for (ModpackConfiguration.FileInformation file : remoteManifest.getFiles()) {
Path actualPath = rootPath.resolve(file.getPath()).toAbsolutePath().normalize();
String fileName = actualPath.getFileName().toString();
if (!actualPath.startsWith(rootPath)) {
throw new IOException("Unsecure path: " + file.getPath());
}
boolean download;
boolean isModDisabled = modsDirectory.equals(actualPath.getParent()) &&
(Files.exists(actualPath.resolveSibling(fileName + LocalAddonManager.DISABLED_EXTENSION)) ||
Files.exists(actualPath.resolveSibling(fileName + LocalAddonManager.OLD_EXTENSION)));
if (isModDisabled) {
download = false;
} else if (!files.containsKey(file.getPath())) {
// If old modpack does not have this entry, download it
download = true;
} else if (!Files.exists(actualPath)) {
// If both old and new modpacks have this entry, but the file is missing...
// Re-download it since network problem may cause file missing
download = true;
} else {View on GitHub (pinned to 24702dc5a0)