{"record":{"id":"9a037f74e3632245","repo":"alibaba/spring-ai-alibaba","slug":"path-outside-root-directory-9a037f","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/GrepSearchTool.java","lineNumber":269,"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 boolean isValidIncludePattern(String pattern) {\n\t\tif (pattern == null || pattern.isEmpty()) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check for invalid characters\n\t\treturn !pattern.contains(\"\\0\") && !pattern.contains(\"\\n\") && !pattern.contains(\"\\r\");\n\t}\n\n\tprivate boolean matchIncludePattern(String filename, String pattern) {\n\t\t// Simple glob matching - convert glob to regex\n\t\t// This is a simplified version; for production use a proper glob library\n\t\tString regex = pattern","sourceCodeStart":251,"sourceCodeEnd":287,"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/GrepSearchTool.java#L251-L287","documentation":"GrepSearchTool confines all searches to a configured root directory. validateAndResolvePath strips the leading '/' from the virtual path, resolves it against rootPath, normalizes it, and throws this IOException when the resolved path no longer starts with rootPath. This is a deliberate security guard against path traversal ('..' segments, symlinks, or absolute paths) escaping the tool's sandboxed workspace.","triggerScenarios":"Calling the grep/search tool with a path containing '..' (e.g. '/../../etc'), a path that resolves via symlinks outside the root, or a path that after normalize() lands outside the tool's configured root directory.","commonSituations":"LLM agents hallucinating absolute filesystem paths like '/etc/passwd' or 'C:\\\\'; users asking the agent to 'search the parent directory'; workspaces with symlinked subdirectories pointing outside root; misconfigured rootPath narrower than the files being searched.","solutions":["Remove any '..' segments or absolute prefixes from the requested path so it stays within the tool's root directory.","If the target files legitimately live outside the sandbox, reconfigure GrepSearchTool's root directory to include them (or move/copy them into the workspace).","Check for symlinks inside the workspace that resolve outside the root and remove or re-point them.","Catch IOException from the tool call and surface the sandbox rule back to the agent so it retries with a relative path."],"exampleFix":"// before\ngrepTool.search(\"/../../etc/hosts\", \"pattern\")\n\n// after\ngrepTool.search(\"/etc/hosts\", \"pattern\") // only if /etc/hosts is inside the tool's root; otherwise copy it into the workspace first","handlingStrategy":"try-catch","validationCode":"String p = requestedPath;\nif (p.contains(\"..\") || (!p.startsWith(\"/\") && !p.isEmpty())) {\n    throw new IllegalArgumentException(\"Path must be root-relative and stay within the workspace: \" + p);\n}","typeGuard":"boolean isSandboxed(String path, Path root) {\n    try {\n        return root.resolve(path.replaceFirst(\"^/\", \"\")).normalize().startsWith(root);\n    } catch (Exception e) { return false; }\n}","tryCatchPattern":"try {\n    String hits = grepTool.search(path, pattern);\n} catch (IOException e) {\n    if (e.getMessage().startsWith(\"Path outside root directory\")) {\n        // inform the agent/user that searches are confined to the workspace root\n    }\n}","preventionTips":["Always pass root-relative virtual paths (starting with '/') that stay inside the configured workspace.","Never construct paths with '..' segments or absolute OS paths when calling the grep tool.","Audit workspaces for symlinks that resolve outside the root.","Set the tool's root directory wide enough to cover all files the agent legitimately needs."],"tags":["security","path-traversal","filesystem","sandbox"],"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"}