{"record":{"id":"3f884f30fa841769","repo":"SonarSource/sonarqube","slug":"directory-s-is-a-symbolic-link","errorCode":null,"errorMessage":"Directory '%s' is a symbolic link","messagePattern":"Directory '(.+?)' is a symbolic link","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"server/sonar-process/src/main/java/org/sonar/process/FileUtils2.java","lineNumber":116,"sourceCode":"    }\n  }\n\n  /**\n   * Deletes a directory recursively. Does not support symbolic link to directories.\n   *\n   * @param directory  directory to delete\n   * @throws IOException in case deletion is unsuccessful\n   */\n  public static void deleteDirectory(File directory) throws IOException {\n    requireNonNull(directory, DIRECTORY_CAN_NOT_BE_NULL);\n\n    if (!directory.exists()) {\n      return;\n    }\n\n    Path path = directory.toPath();\n    if (Files.isSymbolicLink(path)) {\n      throw new IOException(format(\"Directory '%s' is a symbolic link\", directory));\n    }\n    if (directory.isFile()) {\n      throw new IOException(format(\"Directory '%s' is a file\", directory));\n    }\n    deleteDirectoryImpl(path);\n  }\n\n  /**\n   * Size of file or directory, in bytes. In case of a directory,\n   * the size is the sum of the sizes of all files recursively traversed.\n   *\n   * This implementation is recommended over commons-io\n   * {@code FileUtils#sizeOf(File)} which suffers from slow usage of Java IO.\n   *\n   * @throws IOException if files can't be traversed or size attribute is not present\n   * @see BasicFileAttributes#size()\n   */\n  public static long sizeOf(Path path) throws IOException {","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-process/src/main/java/org/sonar/process/FileUtils2.java#L98-L134","documentation":"FileUtils2.deleteDirectory refuses to delete anything that is a symbolic link: before recursing it checks Files.isSymbolicLink and throws an IOException formatted with the directory path. This is a safety guard so a symlink pointing elsewhere (e.g. /) is never followed and recursively deleted.","triggerScenarios":"deleteDirectory (or deleteQuietly) is called with a File that resolves to a symlink, e.g. a temp dir path that is a symlink, or a project/web dir replaced by a symlink during deployment.","commonSituations":"Deployments where /opt/sonarqube/data or temp dirs are symlinked to another volume; users symlinking logs/temp for space management; containers mounting volumes through symlinks.","solutions":["Delete the symlink itself if intended (Files.delete / rm) rather than expecting recursive deletion","Point the configuration at the real directory path instead of a symlinked path","If symlink indirection is required, restructure so the parent directory is real and only contents are linked","Handle/expect the IOException in code that cleans directories and decide explicitly how to treat symlinks"],"exampleFix":"// before\nrm -rf /opt/sonarqube/temp   # temp -> /mnt/bigdisk/temp (symlink)\n// after\nrm /opt/sonarqube/temp        # remove link itself\nrm -rf /mnt/bigdisk/temp/     # clean the real target explicitly","handlingStrategy":"try-catch","validationCode":"if (Files.isSymbolicLink(directory.toPath()))\n  throw new IllegalArgumentException(\"Refusing to delete symlinked dir: \" + directory);","typeGuard":null,"tryCatchPattern":"try { FileUtils2.deleteDirectory(dir); } catch (IOException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"symbolic link\")) {\n    Files.delete(dir.toPath()); // remove the link itself\n  } else { throw e; }\n}","preventionTips":["Avoid symlinking sonarqube data/temp/logs directories to other volumes; use mounts instead","Check for symlinks in deployment layout before automated cleanup","Treat deleteDirectory as refusing links by design; handle that case explicitly","Resolve real paths (toRealPath) in your own tooling before recursive deletes"],"tags":["filesystem","symlink","deletion","safety"],"backgroundTag":"path-traversal-blocked","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}