{"record":{"id":"3bec12ef5f1aec24","repo":"halo-dev/halo","slug":"problemdetail-directorytraversal","errorCode":"problemDetail.directoryTraversal","errorMessage":"Directory traversal detected: {pathToCheck}","messagePattern":"Directory traversal detected: (.+?)","errorType":"http","errorClass":"AccessDeniedException","httpStatus":403,"severity":"critical","filePath":"application/src/main/java/run/halo/app/infra/utils/FileUtils.java","lineNumber":232,"sourceCode":"            }\n        }\n    }\n\n    /**\n     * Checks directory traversal vulnerability.\n     *\n     * @param parentPath parent path must not be null.\n     * @param pathToCheck path to check must not be null\n     */\n    public static void checkDirectoryTraversal(Path parentPath, Path pathToCheck) {\n        Assert.notNull(parentPath, \"Parent path must not be null\");\n        Assert.notNull(pathToCheck, \"Path to check must not be null\");\n\n        if (pathToCheck.normalize().startsWith(parentPath)) {\n            return;\n        }\n\n        throw new AccessDeniedException(\n                \"Directory traversal detected: \" + pathToCheck,\n                \"problemDetail.directoryTraversal\",\n                new Object[] {parentPath, pathToCheck});\n    }\n\n    /**\n     * Checks directory traversal vulnerability.\n     *\n     * @param parentPath parent path must not be null.\n     * @param pathToCheck path to check must not be null\n     */\n    public static void checkDirectoryTraversal(String parentPath, String pathToCheck) {\n        checkDirectoryTraversal(Paths.get(parentPath), Paths.get(pathToCheck));\n    }\n\n    /**\n     * Checks directory traversal vulnerability.\n     *","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/application/src/main/java/run/halo/app/infra/utils/FileUtils.java#L214-L250","documentation":"Thrown as Halo's run.halo.app.infra.exception.AccessDeniedException (carrying problemDetail code 'problemDetail.directoryTraversal') by FileUtils.checkDirectoryTraversal when pathToCheck.normalize() does not start with parentPath. This is a security guard against path-traversal in user-supplied filenames/paths so writes stay inside the allowed parent.","triggerScenarios":"A user/plugin-supplied relative path containing '..' or an absolute path that, once normalized, escapes the configured parent directory; e.g. uploading/reading a file whose name is '../../etc/passwd'.","commonSituations":"Attachment/upload handlers receiving malicious filenames; theme/plugin asset paths built from user input; symbolic links resolving outside the parent; incorrect parentPath base passed by a misconfigured caller.","solutions":["Sanitize the user-supplied name: strip path separators and '..' segments, keep only the basename.","Pass the correct parentPath that actually contains the intended workspace.","Resolve the child against the parent and re-run checkDirectoryTraversal before any I/O.","Reject absolute paths in user input outright."],"exampleFix":"// before\nvar target = Paths.get(fileName); // fileName = ../etc/passwd\nFileUtils.checkDirectoryTraversal(parentDir, target);\n\n// after\nvar safe = parentDir.resolve(Path.of(fileName).getFileName().toString());\nFileUtils.checkDirectoryTraversal(parentDir, safe);","handlingStrategy":"validation","validationCode":"// Reject user paths that escape parent BEFORE any I/O\nString base = Path.of(userPath).getFileName().toString(); // drop all dir info\nPath safe = parentPath.resolve(base).normalize();\nFileUtils.checkDirectoryTraversal(parentPath, safe);","typeGuard":"static boolean isInsideParent(Path parent, Path child) {\n    return child.normalize().startsWith(parent.normalize());\n}","tryCatchPattern":"try {\n    FileUtils.checkDirectoryTraversal(parentPath, pathToCheck);\n} catch (run.halo.app.infra.exception.AccessDeniedException e) {\n    // security control: do NOT echo parentPath to the client; return generic 403\n    return ServerResponse.status(HttpStatus.FORBIDDEN).build();\n}","preventionTips":["Always reduce user filenames to their basename before resolving.","Reject absolute paths and any segment containing '..' from user input.","Re-run checkDirectoryTraversal after resolving symlinks/normalizing.","Never mirror traversal details back to the client."],"tags":["security","path-traversal","filesystem","validation"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}