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

  1. Sanitize the incoming name yourself first: reject or collapse '..' segments (and any absolute prefix) before calling sanitizePath
  2. If your baseDir contains symlinks, pass the canonical directory: new File(baseDir.getCanonicalPath()) so basePath matches what sanitizePath computes internally
  3. 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

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


AI-assisted analysis of iBotPeaches/Apktool@79b63384d7 (2026-08-14). Data as JSON: /api/errors/3389589c62581d14. Report an issue: GitHub.