{"record":{"id":"e286013d8ebca555","repo":"alibaba/arthas","slug":"requestedpath-e28601","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":253,"sourceCode":"            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;\n        }\n        throw new IllegalArgumentException(\"文件不存在或不在允许目录白名单内: \" + requestedPath);\n    }\n\n    private static void assertRegularFile(Path file) {\n        if (!Files.isRegularFile(file)) {\n            throw new IllegalArgumentException(\"不是普通文件: \" + file);\n        }\n    }\n\n    private static boolean isUnderAllowedRoot(Path file, List<Path> allowedRoots) {\n        for (Path root : allowedRoots) {\n            if (file.startsWith(root)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private static int clampMaxBytes(Integer maxBytes) {","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/alibaba/arthas/blob/21cf2e9ba52b305290be7223b980ff504bb9cb5b/core/src/main/java/com/taobao/arthas/core/mcp/tool/function/basic1000/ViewFileTool.java#L235-L271","documentation":"Thrown by resolveAllowedFile when a relative path cannot be matched against any configured allowed root directory. The tool resolves the path against each root in ARTHAS_MCP_VIEWFILE_ALLOWED_DIRS plus the defaults (arthas-output, ~/logs/), and rejects it if no candidate both exists and stays inside a root after symlink normalization (toRealPath). This guards against path-traversal and access outside whitelisted directories.","triggerScenarios":"Calling the viewfile MCP tool with a relative path that (a) does not exist under any allowed root, (b) normalizes outside all roots via '..' segments, or (c) resolves through a symlink that escapes every root. The absolute-path branch (line 229-235) has a separate, slightly different message.","commonSituations":"ARTHAS_MCP_VIEWFILE_ALLOWED_DIRS env var is unset or misconfigured; the target file lives outside arthas-output or ~/logs/; a typo or wrong relative path; path traversal attempt like '../../etc/passwd'.","solutions":["Set ARTHAS_MCP_VIEWFILE_ALLOWED_DIRS=/your/dir to include the directory containing the target file (comma-separated for multiple).","Verify the file actually exists: use an absolute path that is inside a configured root.","Check for typos and stray '..' segments in the relative path.","If defaults are expected, confirm arthas-output/ or ~/logs/ actually exist as directories in the JVM working directory."],"exampleFix":"// before: file is outside all allowed roots\nviewfile(path=\"/var/log/app.log\")\n// after: add the directory to the whitelist env var, then call\n//   ARTHAS_MCP_VIEWFILE_ALLOWED_DIRS=/var/log\nviewfile(path=\"/var/log/app.log\")","handlingStrategy":"validation","validationCode":"// Before calling viewfile, verify the path resolves under an allowed root\nString allowedDirs = System.getenv(\"ARTHAS_MCP_VIEWFILE_ALLOWED_DIRS\");\nList<Path> roots = new ArrayList<>();\nif (allowedDirs != null && !allowedDirs.isBlank()) {\n    for (String d : allowedDirs.split(\",\")) {\n        Path p = Paths.get(d.trim()).toAbsolutePath().normalize();\n        if (Files.isDirectory(p)) roots.add(p.toRealPath());\n    }\n}\nPath target = Paths.get(requestedPath);\nif (!target.isAbsolute()) {\n    boolean found = false;\n    for (Path root : roots) {\n        Path candidate = root.resolve(target).normalize();\n        if (candidate.startsWith(root) && Files.exists(candidate)) { found = true; break; }\n    }\n    if (!found) throw new IllegalStateException(\"Path not under any allowed root: \" + requestedPath);\n} else {\n    Path real = target.toRealPath();\n    if (roots.stream().noneMatch(real::startsWith))\n        throw new IllegalStateException(\"Path not under any allowed root: \" + requestedPath);\n}","typeGuard":null,"tryCatchPattern":"// The tool itself wraps everything in try-catch (line 96-99) and returns\n// a JSON error response string, so the MCP caller receives a string, not an exception.\n// If calling resolveAllowedFile directly:\ntry {\n    Path file = resolveAllowedFile(path, allowedRoots);\n} catch (IllegalArgumentException e) {\n    // handle: path not in whitelist or does not exist\n    logger.warn(\"viewfile path rejected: {}\", e.getMessage());\n}","preventionTips":["Always set ARTHAS_MCP_VIEWFILE_ALLOWED_DIRS in production to include the log directories you need.","Prefer absolute paths when calling viewfile to get clearer error messages.","Validate the path exists and is under a configured root before invoking the tool."],"tags":["viewfile","path-validation","arthas-mcp","filesystem","whitelist"],"backgroundTag":null,"analyzedSha":"21cf2e9ba52b305290be7223b980ff504bb9cb5b","analyzedAt":"2026-08-14T00:57:07.243Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}