{"record":{"id":"3389589c62581d14","repo":"iBotPeaches/Apktool","slug":"path-traverses-outside-the-base-directory","errorCode":null,"errorMessage":"Path traverses outside the base directory","messagePattern":"Path traverses outside the base directory","errorType":"validation","errorClass":"InvalidPathException","httpStatus":null,"severity":"error","filePath":"brut.j.util/src/main/java/brut/util/BrutIO.java","lineNumber":114,"sourceCode":"            crc.update(buffer, 0, bytesRead);\n        }\n        return crc;\n    }\n\n    public static String sanitizePath(File baseDir, String path) throws InvalidPathException, IOException {\n        if (path == null || path.isEmpty()) {\n            throw new InvalidPathException(path, \"Path is null or empty\");\n        }\n\n        Path origPath = Paths.get(path);\n        if (origPath.isAbsolute()) {\n            throw new InvalidPathException(path, \"Absolute paths are not allowed\");\n        }\n\n        Path basePath = Paths.get(baseDir.getCanonicalPath());\n        Path resolvedPath = basePath.resolve(origPath).normalize();\n        if (!resolvedPath.startsWith(basePath)) {\n            throw new InvalidPathException(path, \"Path traverses outside the base directory\");\n        }\n\n        return basePath.relativize(resolvedPath).toString();\n    }\n}\n","sourceCodeStart":96,"sourceCodeEnd":120,"githubUrl":"https://github.com/iBotPeaches/Apktool/blob/79b63384d7d7e22917e6ea8b453272da7012515b/brut.j.util/src/main/java/brut/util/BrutIO.java#L96-L120","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nString rel = BrutIO.sanitizePath(baseDir, entry.getName()); // '../../etc/cron.d/x'\n\n// after\nString name = entry.getName();\nif (name.contains(\"..\") || Paths.get(name).isAbsolute()) {\n    throw new IOException(\"Illegal entry name: \" + name);\n}\nString rel = BrutIO.sanitizePath(baseDir, name);","handlingStrategy":"validation","validationCode":"String safeEntry(String name, File baseDir) throws IOException {\n    if (name == null || name.isEmpty()) return null;\n    if (Paths.get(name).isAbsolute() || name.split(\"[/\\\\\\\\]\").length > 0\n            && java.util.Arrays.stream(name.split(\"[/\\\\\\\\]\")).anyMatch(\"..\"::equals)) {\n        return null; // quarantined entry\n    }\n    return BrutIO.sanitizePath(baseDir, name);\n}","typeGuard":null,"tryCatchPattern":"try {\n    rel = BrutIO.sanitizePath(baseDir, name);\n} catch (InvalidPathException e) {\n    log.warn(\"skipping unsafe entry {}\", name); // skip, never write outside baseDir\n}","preventionTips":["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"],"tags":["path-traversal","security","zip-slip","filesystem","java"],"backgroundTag":null,"analyzedSha":"79b63384d7d7e22917e6ea8b453272da7012515b","analyzedAt":"2026-08-14T10:43:28.812Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}