From 5ec2990b5d1699263bdcfac7e2c60299f5cc59ec Mon Sep 17 00:00:00 2001 From: Rakesh Kumar Singh Date: Wed, 20 May 2026 16:09:52 +0530 Subject: [PATCH] NIFI-15653 Fix DeleteSFTP security check for dot directory path When the Remote Path property is set to '.' (as produced by ListSFTP), Paths.get('.').normalize() returns an empty Path (''). Resolving a filename against that empty path yields a single-component relative path whose getParent() returns null, causing the directory-traversal security check to fail incorrectly and route every FlowFile to the failure relationship. Fix: when fileParent is null (single-component relative path), substitute Paths.get('') for the comparison. Both the empty normalized path and a null parent represent the implicit current directory, so the check now correctly passes. Path-traversal attempts (e.g. '../etc/passwd') still produce a non-empty parent that does not equal the empty directoryPath, so the security guard remains intact. Add TestDeleteSFTP.deletesFileWhenDirectoryPathIsDot() to cover the regression. --- .../apache/nifi/processors/standard/DeleteSFTP.java | 3 ++- .../nifi/processors/standard/TestDeleteSFTP.java | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/DeleteSFTP.java b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/DeleteSFTP.java index 5d284d3437c6..29b6845b7bbc 100644 --- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/DeleteSFTP.java +++ b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/DeleteSFTP.java @@ -211,7 +211,8 @@ public void onTrigger(ProcessContext context, ProcessSession session) throws Pro filename = context.getProperty(FILENAME).evaluateAttributeExpressions(flowFile).getValue(); final Path filePath = directoryPath.resolve(filename).normalize(); - if (!directoryPath.equals(filePath.getParent())) { + final Path fileParent = filePath.getParent(); + if (!directoryPath.equals(fileParent == null ? Paths.get("") : fileParent)) { final String errorMessage = "Attempting to delete file at path '%s' which is not a direct child of the directory '%s'" .formatted(filePath, directoryPath); diff --git a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestDeleteSFTP.java b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestDeleteSFTP.java index eeb91b824754..0ce6c5b50cd3 100644 --- a/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestDeleteSFTP.java +++ b/nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestDeleteSFTP.java @@ -154,6 +154,18 @@ void sendsFlowFileToSuccessWhenDeletingADirectoryAndDirectoryIsEmpty() throws IO runner.assertAllFlowFilesTransferred(DeleteSFTP.REL_SUCCESS); } + @Test + void deletesFileWhenDirectoryPathIsDot() throws IOException { + final Path fileToDelete = Files.writeString(sshServerRootPath.resolve("test.txt"), "some text"); + enqueue(".", fileToDelete.getFileName().toString()); + assertExists(fileToDelete); + + runner.run(); + + assertNotExists(fileToDelete); + runner.assertAllFlowFilesTransferred(DeleteSFTP.REL_SUCCESS, 1); + } + @Test void sendsFlowFileToFailureWhenFileIsNotADirectChildOfTheDirectory() throws IOException { final Path directoryPath = Files.createDirectories(sshServerRootPath.resolve("rel/path"));