{"record":{"id":"6eb4c3f7b789e608","repo":"OtterMind/Chat2DB","slug":"symbolic-links-are-not-supported","errorCode":null,"errorMessage":"Symbolic links are not supported","messagePattern":"Symbolic links are not supported","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"warning","filePath":"chat2db-community-server/chat2db-community-jcef/src/main/java/ai/chat2db/community/jcef/handler/biz/SqlDirectoryTreeStore.java","lineNumber":176,"sourceCode":"        if (!realTarget.startsWith(root)) {\n            throw new IllegalArgumentException(\"Path is outside of the selected SQL directory\");\n        }\n\n        Map<String, Object> result = new HashMap<>();\n        result.put(\"createdNode\", toNode(rootToken, root, realTarget));\n        result.put(\"children\", listChildren(rootToken, parentRelativePath));\n        return result;\n    }\n\n    static Map<String, Object> renameChild(String rootToken, String relativePath, String rawName)\n            throws IOException {\n        Path root = getRoot(rootToken);\n        Path source = resolveInRoot(root, relativePath);\n        if (source.equals(root)) {\n            throw new IllegalArgumentException(\"Selected SQL directory root cannot be renamed\");\n        }\n        if (Files.isSymbolicLink(source)) {\n            throw new IllegalArgumentException(\"Symbolic links are not supported\");\n        }\n\n        boolean file = Files.isRegularFile(source, LinkOption.NOFOLLOW_LINKS);\n        boolean directory = Files.isDirectory(source, LinkOption.NOFOLLOW_LINKS);\n        if (!file && !directory) {\n            throw new IllegalArgumentException(\"Selected path is not available\");\n        }\n        String sourceFileName = source.getFileName().toString();\n        String fallbackExtension = file ? getFileExtension(sourceFileName) : \"\";\n        String name = file ? normalizeExistingFileName(rawName, fallbackExtension)\n                : normalizeDirectoryName(rawName);\n        Path parent = source.getParent();\n        Path target = parent.resolve(name).normalize();\n        if (!target.startsWith(root)) {\n            throw new IllegalArgumentException(\"Path is outside of the selected SQL directory\");\n        }\n        if (Files.exists(target, LinkOption.NOFOLLOW_LINKS) && !source.equals(target)) {\n            throw new IllegalArgumentException(\"File or directory already exists\");","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/OtterMind/Chat2DB/blob/5ee1e990e73fbcae1969dc554be254fedb3ab888/chat2db-community-server/chat2db-community-jcef/src/main/java/ai/chat2db/community/jcef/handler/biz/SqlDirectoryTreeStore.java#L158-L194","documentation":"Thrown by SqlDirectoryTreeStore.renameChild() when the source path is a symbolic link. Symlinks are intentionally excluded from all tree operations for security: they can escape the root containment boundary. isVisibleChild() already filters symlinks from listings, so this guard catches symlinks that appeared after listing or were created by an external process between listing and rename.","triggerScenarios":"The source path resolved by resolveInRoot is a symbolic link. Since resolveInRoot walks each path segment and rejects symlinks (line 447-449), this check at line 175 is a second guard — it can only fire if a symlink was created at the exact source path after resolveInRoot's segment walk but before the isSymbolicLink check, which is a TOCTOU race.","commonSituations":"Extremely rare due to double-guarding. Could fire if an external process creates a symlink at the source path during the rename operation; a concurrent operation replaced the source with a symlink.","solutions":["This is a security guard — inform the user that symbolic links are not supported in the SQL directory","Refresh the tree and check if the source still exists as a regular file or directory","Do not attempt to bypass this guard — symlinks are intentionally blocked for path-traversal safety"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before calling renameChild, verify the source is not a symlink\nif (Files.isSymbolicLink(source)) {\n    throw new IllegalArgumentException(\"Cannot rename a symbolic link\");\n}\nSqlDirectoryTreeStore.renameChild(rootToken, relativePath, rawName);","typeGuard":null,"tryCatchPattern":"try {\n    Map<String, Object> result = SqlDirectoryTreeStore.renameChild(rootToken, relativePath, name);\n    ResponseBuilder.buildSuccessJcef(Map.of(\"data\", result), callback);\n} catch (IllegalArgumentException e) {\n    callback.failure(400, e.getMessage());\n}","preventionTips":["Symlinks are filtered from tree listings (isVisibleChild returns false) — if one appears, the tree is stale","Refresh the tree if a symlink is detected at rename time","Do not attempt to bypass this guard — symlinks are blocked for path-traversal safety"],"tags":["sql-directory","security","symlink","rename","defense-in-depth"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}