{"record":{"id":"5d7d2e73628f584b","repo":"alibaba/spring-ai-alibaba","slug":"path-outside-root-directory-5d7d2e","errorCode":null,"errorMessage":"Path outside root directory: ","messagePattern":"Path outside root directory: ","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"warning","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/tools/GlobSearchTool.java","lineNumber":178,"sourceCode":"\n\tprivate Path validateAndResolvePath(String path) throws IOException {\n\t\t// Normalize path\n\t\tif (!path.startsWith(\"/\")) {\n\t\t\tpath = \"/\" + path;\n\t\t}\n\n\t\t// Check for path traversal\n\t\tif (path.contains(\"..\") || path.contains(\"~\")) {\n\t\t\tthrow new IOException(\"Path traversal not allowed\");\n\t\t}\n\n\t\t// Convert virtual path to filesystem path\n\t\tString relative = path.substring(1); // Remove leading /\n\t\tPath fullPath = rootPath.resolve(relative).normalize();\n\n\t\t// Ensure path is within root\n\t\tif (!fullPath.startsWith(rootPath)) {\n\t\t\tthrow new IOException(\"Path outside root directory: \" + path);\n\t\t}\n\n\t\treturn fullPath;\n\t}\n\n\tprivate record FileInfo(String path, Instant modifiedTime) {}\n\n\tpublic static Builder builder(String rootPath) {\n\t\treturn new Builder(rootPath);\n\t}\n\n\tpublic static class Builder {\n\n\t\tprivate final String rootPath;\n\n\t\tprivate String name = \"glob_search\";\n\n\t\tprivate String description = \"Fast file pattern matching tool that works with any codebase size. \"","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/tools/GlobSearchTool.java#L160-L196","documentation":"After resolving and normalizing the virtual path against rootPath, GlobSearchTool.validateAndResolvePath() throws IOException \"Path outside root directory: <path>\" when the normalized full path does not start with the root. This is the second line of defense after the literal .. check, catching symlink/normalization escapes.","triggerScenarios":"A path that normalizes outside rootPath — e.g. through symlinks inside the root pointing elsewhere, or encodings/tricks that survive the \"..\" substring check but resolve outside.","commonSituations":"Symlinked subdirectories in the workspace pointing to other locations, misconfigured rootPath (relative paths resolved differently than expected), tools called with paths intended for a different tool instance.","solutions":["Pass paths that resolve within the configured root directory.","Ensure rootPath is absolute and toRealPath()-normalized so symlink resolution matches the check.","Remove or re-point symlinks inside the root that escape the workspace.","Catch IOException and surface a tool error prompting the model to use a valid path."],"exampleFix":"// before\nPath root = Paths.get(\"workspace\"); // relative — checks may misalign\n// after\nPath root = Paths.get(\"workspace\").toAbsolutePath().normalize();","handlingStrategy":"validation","validationCode":"Path root = Paths.get(rootDir).toAbsolutePath().normalize();\nPath target = root.resolve(relPath).normalize();\nif (!target.startsWith(root)) { throw new IllegalArgumentException(\"path escapes root\"); }","typeGuard":"boolean insideRoot(Path root, Path p) { return p.toAbsolutePath().normalize().startsWith(root.toAbsolutePath().normalize()); }","tryCatchPattern":"try { tool.glob(pattern, path); } catch (IOException e) { if (e.getMessage().startsWith(\"Path outside root\")) { /* retry with root-relative path */ } }","preventionTips":["Configure rootPath as an absolute normalized path","Audit symlinks inside the root","Pre-check resolved paths against the root","Keep tool roots aligned with the workspace"],"tags":["security","path-traversal","file-search","symlink"],"backgroundTag":"path-traversal-blocked","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}