{"record":{"id":"3db95d9d5259b594","repo":"alibaba/arthas","slug":"requestedpath","errorCode":null,"errorMessage":"文件不在允许目录白名单内: ${requestedPath}","messagePattern":"文件不在允许目录白名单内: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/com/taobao/arthas/core/mcp/tool/function/basic1000/ViewFileTool.java","lineNumber":233,"sourceCode":"\n        return deduplicate(roots);\n    }\n\n    private static List<Path> deduplicate(List<Path> roots) {\n        if (roots == null || roots.isEmpty()) {\n            return Collections.emptyList();\n        }\n        LinkedHashSet<Path> set = new LinkedHashSet<>(roots);\n        return new ArrayList<>(set);\n    }\n\n    private Path resolveAllowedFile(String requestedPath, List<Path> allowedRoots) throws Exception {\n        Path req = Paths.get(requestedPath);\n        if (req.isAbsolute()) {\n            Path real = req.toRealPath();\n            assertRegularFile(real);\n            if (!isUnderAllowedRoot(real, allowedRoots)) {\n                throw new IllegalArgumentException(\"文件不在允许目录白名单内: \" + requestedPath);\n            }\n            return real;\n        }\n\n        for (Path root : allowedRoots) {\n            Path candidate = root.resolve(req).normalize();\n            if (!candidate.startsWith(root)) {\n                continue;\n            }\n            if (!Files.exists(candidate)) {\n                continue;\n            }\n            Path real = candidate.toRealPath();\n            if (!real.startsWith(root)) {\n                continue;\n            }\n            assertRegularFile(real);\n            return real;","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/alibaba/arthas/blob/21cf2e9ba52b305290be7223b980ff504bb9cb5b/core/src/main/java/com/taobao/arthas/core/mcp/tool/function/basic1000/ViewFileTool.java#L215-L251","documentation":"Thrown by resolveAllowedFile() in the ViewFile MCP tool when the requested absolute path resolves (via toRealPath, following symlinks) to a file that is not located under any configured allowed root directory. This is the whitelist boundary: viewfile only reads from roots in the ARTHAS_VIEWFILE_ALLOWED_DIRS env var plus the default roots (arthas-output/, ~/logs/).","triggerScenarios":"Calling viewfile with an absolute path outside every allowed root, e.g. path=/etc/passwd when no allowed root covers /etc. toRealPath resolves symlinks before the check, so symlink escapes are also caught.","commonSituations":"Trying to read a log/config file outside the default roots without configuring ARTHAS_VIEWFILE_ALLOWED_DIRS; symlink pointing outside the allowed tree; expecting arthas-output or ~/logs to cover a path it does not.","solutions":["Add the parent directory of the target file to the ARTHAS_VIEWFILE_ALLOWED_DIRS env var (comma-separated) before starting arthas, then retry.","Move/copy the file under an already-allowed root (arthas-output/ or ~/logs/).","Use a relative path that resolves under an allowed root instead of an absolute one.","Confirm the resolved real path (after symlinks) is actually under a configured root."],"exampleFix":"// before\nviewfile(path=\"/etc/myapp/config.yaml\")\n// after\n// export ARTHAS_VIEWFILE_ALLOWED_DIRS=/etc/myapp before launching arthas\nviewfile(path=\"/etc/myapp/config.yaml\")","handlingStrategy":"validation","validationCode":"// Verify the target resolves under an allowed root before calling viewfile\nPath real = Paths.get(requestedPath).toRealPath();\nboolean allowed = loadAllowedRoots().stream().anyMatch(root -> real.startsWith(root));\nif (!allowed) throw new IllegalArgumentException(\"path outside allowed roots: \" + requestedPath);","typeGuard":"static boolean isUnderAllowedRoot(Path file, List<Path> roots) {\n    Path real;\n    try { real = file.toRealPath(); } catch (IOException e) { return false; }\n    return roots.stream().anyMatch(root -> real.startsWith(root));\n}","tryCatchPattern":"null","preventionTips":["Configure ARTHAS_VIEWFILE_ALLOWED_DIRS to include the directories you need to read.","Prefer relative paths under an existing allowed root (arthas-output/, ~/logs/).","Remember toRealPath follows symlinks, so symlink targets must also be under an allowed root.","Set the env var before launching arthas; it is read at tool initialisation."],"tags":["arthas","mcp","viewfile","security","whitelist","path-traversal"],"backgroundTag":null,"analyzedSha":"21cf2e9ba52b305290be7223b980ff504bb9cb5b","analyzedAt":"2026-08-14T00:57:07.243Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}