{"record":{"id":"e8548298abe9fdfe","repo":"alibaba/spring-ai-alibaba","slug":"path-outside-root-directory","errorCode":null,"errorMessage":"Path: outside root directory: ","messagePattern":"Path: outside root directory: ","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"warning","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/extension/file/LocalFilesystemBackend.java","lineNumber":109,"sourceCode":"\t}\n\n\t/**\n\t * Resolve a file path with security checks.\n\t *\n\t * When virtualMode=True, treat incoming paths as virtual absolute paths under\n\t * cwd, disallow traversal (.., ~) and ensure resolved path stays within root.\n\t * When virtualMode=False, preserve legacy behavior: absolute paths are allowed\n\t * as-is; relative paths resolve under cwd.\n\t */\n\tprivate Path resolvePath(String key) throws IllegalArgumentException {\n\t\tif (virtualMode) {\n\t\t\tString vpath = key.startsWith(\"/\") ? key : \"/\" + key;\n\t\t\tif (vpath.contains(\"..\") || vpath.startsWith(\"~\")) {\n\t\t\t\tthrow new IllegalArgumentException(\"Path traversal not allowed\");\n\t\t\t}\n\t\t\tPath full = cwd.resolve(vpath.substring(1)).normalize();\n\t\t\tif (!full.startsWith(cwd)) {\n\t\t\t\tthrow new IllegalArgumentException(\"Path:\" + full + \" outside root directory: \" + cwd);\n\t\t\t}\n\t\t\treturn full;\n\t\t}\n\n\t\tPath path = Paths.get(key);\n\t\tif (path.isAbsolute()) {\n\t\t\treturn path;\n\t\t}\n\t\treturn cwd.resolve(path).normalize();\n\t}\n\n\t@Override\n\tpublic List<FileInfo> lsInfo(String path) {\n\t\ttry {\n\t\t\tPath dirPath = resolvePath(path);\n\t\t\tif (!Files.exists(dirPath) || !Files.isDirectory(dirPath)) {\n\t\t\t\treturn Collections.emptyList();\n\t\t\t}","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/extension/file/LocalFilesystemBackend.java#L91-L127","documentation":"In virtualMode, after resolving and normalizing the requested key against the backend's cwd, resolvePath() verifies the result still starts with cwd; if normalization (e.g. via symlinks-in-path or .. already caught, but also absolute segments) escapes the root it throws IllegalArgumentException(\"Path:<full> outside root directory: <cwd>\"). This is the second layer of the virtual-root sandbox.","triggerScenarios":"resolvePath() (via dirPath/resolvedPath/searchPath/grepRaw) with a key whose normalized absolute form falls outside the configured virtual root directory.","commonSituations":"Configuring a cwd narrower than the paths the application actually needs; joining user input with the root such that the combined path points elsewhere; storing absolute external paths as keys.","solutions":["Use paths located under the configured cwd/root directory.","Reconfigure the backend's cwd (virtual root) to encompass the required directory tree.","Normalize and prefix external absolute paths relative to the root before passing them.","Check the normalized path with full.startsWith(root) in your own code first for a clearer error message."],"exampleFix":"// before\nbackend.readFile(\"/etc/hosts\"); // outside virtual root\n\n// after\nPath root = Path.of(\"/srv/agent-data\");\nPath p = root.resolve(\"etc/hosts\").normalize();\nif (!p.startsWith(root)) throw new IllegalArgumentException(\"outside root\");\nbackend.readFile(root.relativize(p).toString());","handlingStrategy":"validation","validationCode":"Path root = backendRoot;\nPath p = root.resolve(key).normalize();\nif (!p.startsWith(root)) {\n    throw new IllegalArgumentException(\"Resolved path outside root: \" + p);\n}","typeGuard":"boolean isInsideRoot(String key, Path root) {\n    return root.resolve(key).normalize().startsWith(root);\n}","tryCatchPattern":"try {\n    backend.readFile(key);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Path:\")) {\n        log.error(\"Key escaped virtual root {}\", e.getMessage());\n    }\n    throw e;\n}","preventionTips":["Configure the virtual root (cwd) wide enough for all legitimate operations.","Convert external absolute paths into root-relative keys before calling the backend.","Test join-and-normalize logic with adversarial inputs in CI.","Avoid storing absolute external paths as backend keys."],"tags":["security","sandbox","filesystem"],"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"}