{"record":{"id":"979088fee0f40149","repo":"alibaba/spring-ai-alibaba","slug":"resolved-path-escapes-root-directory","errorCode":null,"errorMessage":"resolved path escapes root directory","messagePattern":"resolved path escapes root directory","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"warning","filePath":"spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/store/stores/FileSystemStore.java","lineNumber":264,"sourceCode":"\t\t}\n\t}\n\n\t/**\n\t * Create item path from namespace and key.\n\t * @param namespace namespace\n\t * @param key key\n\t * @return item path\n\t */\n\tprivate Path createItemPath(List<String> namespace, String key) {\n\t\tPath path = rootPath.toAbsolutePath().normalize();\n\t\tfor (String ns : namespace) {\n\t\t\tvalidatePathSegment(ns, \"namespace\");\n\t\t\tpath = path.resolve(ns);\n\t\t}\n\t\tvalidatePathSegment(key, \"key\");\n\t\tPath itemPath = path.resolve(key + \".json\").normalize();\n\t\tif (!itemPath.startsWith(rootPath.toAbsolutePath().normalize())) {\n\t\t\tthrow new IllegalArgumentException(\"resolved path escapes root directory\");\n\t\t}\n\t\treturn itemPath;\n\t}\n\n\tprivate void validatePathSegment(String segment, String fieldName) {\n\t\tif (segment == null || segment.trim().isEmpty()) {\n\t\t\tthrow new IllegalArgumentException(fieldName + \" cannot be null or empty\");\n\t\t}\n\t\tPath candidate = Paths.get(segment);\n\t\tif (candidate.isAbsolute() || candidate.getNameCount() != 1 || \"..\".equals(segment) || \".\".equals(segment)) {\n\t\t\tthrow new IllegalArgumentException(fieldName + \" contains unsafe path segment: \" + segment);\n\t\t}\n\t}\n\n\t/**\n\t * Ensure directory exists.\n\t * @param directory directory to create\n\t */","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/store/stores/FileSystemStore.java#L246-L282","documentation":"FileSystemStore.createItemPath resolves namespace+key into a path under the store root and rejects the result if it does not start with the normalized absolute root path. This IllegalArgumentException guards against path traversal, e.g. a key containing '..' or an absolute path component that survives normalization.","triggerScenarios":"Calling putItem/getItem/deleteItem/search with a namespace or key that normalizes outside the root (e.g. key '../escape', namespace containing '.' or '..' segments that slip past validatePathSegment on unusual platforms).","commonSituations":"Passing untrusted user input as namespace/key; keys built by string concatenation from request parameters; upstream validation removed leading '../' but not interior ones.","solutions":["Sanitize namespace/key to [A-Za-z0-9._-] before calling store APIs","Reject keys containing path separators or '..' in your own validation layer","Catch IllegalArgumentException and return a 400-style error to the caller","Use fixed identifiers (UUIDs) instead of raw user strings as keys"],"exampleFix":"// before\nstore.putItem(List.of(userInputNs), userInputKey, value);\n// after\nif (!userInputKey.matches(\"[A-Za-z0-9._-]+\")) {\n    throw new IllegalArgumentException(\"invalid key\");\n}\nstore.putItem(List.of(userInputNs), userInputKey, value);","handlingStrategy":"validation","validationCode":"static boolean safeSegment(String s) {\n    return s != null && s.matches(\"[A-Za-z0-9][A-Za-z0-9._-]*\") && !s.equals(\"..\") && !s.equals(\".\");\n}\n// call for every namespace element and the key before store APIs","typeGuard":null,"tryCatchPattern":"try {\n    store.getItem(namespace, key);\n} catch (IllegalArgumentException e) {\n    throw new BadRequestException(\"invalid store key\");\n}","preventionTips":["Never pass raw user input as namespace/key","Generate keys (UUID/hash) server-side","Keep this guard even after library updates"],"tags":["security","path-traversal","validation"],"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"}