iBotPeaches/Apktool · error · InvalidPathException
Path traverses outside the base directory
Error message
Path traverses outside the base directory
What it means
Thrown by BrutIO.sanitizePath when resolving the (relative) path against baseDir's canonical path escapes the base directory after normalization. This is the classic zip-slip / path-traversal defense: segments like '..' that normalize above the base are rejected. The method exists so untrusted archive entry names can never write outside the intended output directory.
Source
Thrown at brut.j.util/src/main/java/brut/util/BrutIO.java:114
crc.update(buffer, 0, bytesRead);
}
return crc;
}
public static String sanitizePath(File baseDir, String path) throws InvalidPathException, IOException {
if (path == null || path.isEmpty()) {
throw new InvalidPathException(path, "Path is null or empty");
}
Path origPath = Paths.get(path);
if (origPath.isAbsolute()) {
throw new InvalidPathException(path, "Absolute paths are not allowed");
}
Path basePath = Paths.get(baseDir.getCanonicalPath());
Path resolvedPath = basePath.resolve(origPath).normalize();
if (!resolvedPath.startsWith(basePath)) {
throw new InvalidPathException(path, "Path traverses outside the base directory");
}
return basePath.relativize(resolvedPath).toString();
}
}
View on GitHub (pinned to 79b63384d7)
Solutions
- Sanitize the incoming name yourself first: reject or collapse '..' segments (and any absolute prefix) before calling sanitizePath
- If your baseDir contains symlinks, pass the canonical directory: new File(baseDir.getCanonicalPath()) so basePath matches what sanitizePath computes internally
- For archive extraction, verify each entry name with a check like !entry.getName().contains("..") and skip or quarantine offending entries
Example fix
// before
String rel = BrutIO.sanitizePath(baseDir, entry.getName()); // '../../etc/cron.d/x'
// after
String name = entry.getName();
if (name.contains("..") || Paths.get(name).isAbsolute()) {
throw new IOException("Illegal entry name: " + name);
}
String rel = BrutIO.sanitizePath(baseDir, name); Defensive patterns
Strategy: validation
Validate before calling
String safeEntry(String name, File baseDir) throws IOException {
if (name == null || name.isEmpty()) return null;
if (Paths.get(name).isAbsolute() || name.split("[/\\\\]").length > 0
&& java.util.Arrays.stream(name.split("[/\\\\]")).anyMatch(".."::equals)) {
return null; // quarantined entry
}
return BrutIO.sanitizePath(baseDir, name);
} Try / catch
try {
rel = BrutIO.sanitizePath(baseDir, name);
} catch (InvalidPathException e) {
log.warn("skipping unsafe entry {}", name); // skip, never write outside baseDir
} Prevention
- Treat archive entry names as hostile: reject '..' and absolute names at the loop boundary, not deep in extraction code
- Pass baseDir through getCanonicalPath() yourself when your tree contains symlinks, so your notion of 'inside' matches the library's
- Add a zip-slip regression test with entries like '../../escape.txt' to your suite
When it happens
Trigger: Calling sanitizePath(baseDir, path) where basePath.resolve(origPath).normalize() no longer startsWith(basePath) — e.g. '../secrets.txt', 'a/../../b', or a symlink under baseDir whose canonical target lies outside baseDir (getCanonicalPath resolves symlinks, so a symlinked baseDir shifts basePath).
Common situations: Extracting malicious or malformed archives containing '..' entries (zip-slip attacks); output directories reached through symlinks whose canonical location differs from the given baseDir; baseDir passed as a relative path while entries are relative to a different root; renamed/moved directories where the canonical path changed.
Related errors
- Path is null or empty
- Absolute paths are not allowed
- Malicious value for apkFileName: " + mApkFileName
- Could not move file:
- Could not copy file:
AI-assisted analysis of iBotPeaches/Apktool@79b63384d7 (2026-08-14).
Data as JSON: /api/errors/3389589c62581d14.
Report an issue: GitHub.