This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch camel-4.14.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.14.x by this push:
new 2f0ec28fbcea [backport camel-4.14.x] CAMEL-23765: remote-file
consumers - contain localWorkDirectory downloads within the work directory
(#24220)
2f0ec28fbcea is described below
commit 2f0ec28fbcea753609b581e8bacfc18acd96c9b7
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Jun 24 15:20:36 2026 +0200
[backport camel-4.14.x] CAMEL-23765: remote-file consumers - contain
localWorkDirectory downloads within the work directory (#24220)
CAMEL-23765: remote-file consumers - contain localWorkDirectory downloads
within the work directory
Backport of #24180 to camel-4.14.x.
When localWorkDirectory was enabled, the remote-file consumers built the
local
work file path from the remote file name without ensuring the result stayed
within the configured work directory, so a remote file name containing ../
sequences could resolve to a path outside it. A shared
GenericFileHelper.jailToLocalWorkDirectory containment check (compactPath +
startsWith, mirroring the file producer's jail) is now applied to both the
in-progress temp file and the final file, before mkdirs, in FtpOperations,
SftpOperations, FilesOperations and SmbOperations. It reuses the existing
jailStartingDirectory option (default true).
Note: camel-mina-sftp does not exist on camel-4.14.x (added later), so its
MinaSftpOperations change from the main PR is not applicable here.
Source-only
backport: the jailStartingDirectory producer->common label change and its
regenerated metadata are intentionally omitted; the upgrade-guide entry
lives on
main.
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
.../component/file/azure/FilesOperations.java | 8 ++++
.../camel/component/file/GenericFileHelper.java | 23 +++++++++++
.../component/file/GenericFileHelperTest.java | 48 ++++++++++++++++++++++
.../camel/component/file/remote/FtpOperations.java | 8 ++++
.../component/file/remote/SftpOperations.java | 8 ++++
.../apache/camel/component/smb/SmbOperations.java | 12 +++++-
6 files changed, 105 insertions(+), 2 deletions(-)
diff --git
a/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesOperations.java
b/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesOperations.java
index d669187fbbe6..423d03efdaed 100644
---
a/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesOperations.java
+++
b/components/camel-azure/camel-azure-files/src/main/java/org/apache/camel/component/file/azure/FilesOperations.java
@@ -47,6 +47,7 @@ import org.apache.camel.component.file.FileComponent;
import org.apache.camel.component.file.GenericFile;
import org.apache.camel.component.file.GenericFileEndpoint;
import org.apache.camel.component.file.GenericFileExist;
+import org.apache.camel.component.file.GenericFileHelper;
import org.apache.camel.component.file.GenericFileOperationFailedException;
import org.apache.camel.component.file.remote.RemoteFile;
import org.apache.camel.component.file.remote.RemoteFileConfiguration;
@@ -301,9 +302,16 @@ public class FilesOperations extends NormalizedOperations {
"Exchange should have the " +
FileComponent.FILE_EXCHANGE_FILE + " set");
String relativeName = target.getRelativeFilePath();
+ File localWorkDir = local;
inProgress = new File(local, relativeName + ".inprogress");
local = new File(local, relativeName);
+ // ensure the local work file stays within the local work
directory (CAMEL-23765)
+ if (endpoint.isJailStartingDirectory()) {
+ GenericFileHelper.jailToLocalWorkDirectory(inProgress,
localWorkDir);
+ GenericFileHelper.jailToLocalWorkDirectory(local,
localWorkDir);
+ }
+
// create directory to local work file
boolean result = local.mkdirs();
if (!result) {
diff --git
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileHelper.java
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileHelper.java
index b86351c010d6..ba40b2948e78 100644
---
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileHelper.java
+++
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileHelper.java
@@ -16,16 +16,39 @@
*/
package org.apache.camel.component.file;
+import java.io.File;
import java.util.function.Supplier;
import org.apache.camel.Exchange;
import org.apache.camel.support.MessageHelper;
+import org.apache.camel.util.FileUtil;
public final class GenericFileHelper {
private GenericFileHelper() {
}
+ /**
+ * Ensures the resolved local work file stays within the configured local
work directory. The remote file name used
+ * to build the local work file path may contain {@code ../} sequences
that would otherwise resolve to a path
+ * outside the work directory.
+ *
+ * @param target the resolved local work
file (or its in-progress temp file)
+ * @param localWorkDirectory the local work directory
the file must stay within
+ * @throws GenericFileOperationFailedException if the target resolves
outside the local work directory
+ */
+ public static void jailToLocalWorkDirectory(File target, File
localWorkDirectory) {
+ // compact first as the remote relative name can use ../ etc
+ String compactTarget = FileUtil.compactPath(target.getPath());
+ String compactWork =
FileUtil.compactPath(localWorkDirectory.getPath());
+ if (!compactTarget.startsWith(compactWork)) {
+ throw new GenericFileOperationFailedException(
+ "Cannot retrieve file to local work file: " + compactTarget
+ + " as it is jailed
to the local work directory: "
+ + compactWork);
+ }
+ }
+
public static String asExclusiveReadLockKey(GenericFile file, String key) {
// use the copy from absolute path as that was the original path of the
// file when the lock was acquired
diff --git
a/components/camel-file/src/test/java/org/apache/camel/component/file/GenericFileHelperTest.java
b/components/camel-file/src/test/java/org/apache/camel/component/file/GenericFileHelperTest.java
new file mode 100644
index 000000000000..5eefdbfcd907
--- /dev/null
+++
b/components/camel-file/src/test/java/org/apache/camel/component/file/GenericFileHelperTest.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.file;
+
+import java.io.File;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class GenericFileHelperTest {
+
+ private final File workDir = new File("target/localwork");
+
+ @Test
+ public void shouldAllowFilesWithinLocalWorkDirectory() {
+ // a plain name, a nested name, and a ../ that still resolves within
the work directory are all allowed
+ assertDoesNotThrow(() ->
GenericFileHelper.jailToLocalWorkDirectory(new File(workDir, "file.txt"),
workDir));
+ assertDoesNotThrow(() ->
GenericFileHelper.jailToLocalWorkDirectory(new File(workDir,
"sub/dir/file.txt"), workDir));
+ assertDoesNotThrow(() ->
GenericFileHelper.jailToLocalWorkDirectory(new File(workDir,
"sub/../file.txt"), workDir));
+ }
+
+ @Test
+ public void shouldRejectFilesEscapingLocalWorkDirectory() {
+ // a remote file name that resolves outside the configured local work
directory must be rejected
+ assertThrows(GenericFileOperationFailedException.class,
+ () -> GenericFileHelper.jailToLocalWorkDirectory(new
File(workDir, "../escape.txt"), workDir));
+ assertThrows(GenericFileOperationFailedException.class,
+ () -> GenericFileHelper.jailToLocalWorkDirectory(new
File(workDir, "../../etc/passwd"), workDir));
+ assertThrows(GenericFileOperationFailedException.class,
+ () -> GenericFileHelper.jailToLocalWorkDirectory(new
File(workDir, "sub/../../escape.txt"), workDir));
+ }
+}
diff --git
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
index d1dc62fcb5c8..55055192873e 100644
---
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
+++
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/FtpOperations.java
@@ -32,6 +32,7 @@ import org.apache.camel.component.file.FileComponent;
import org.apache.camel.component.file.GenericFile;
import org.apache.camel.component.file.GenericFileEndpoint;
import org.apache.camel.component.file.GenericFileExist;
+import org.apache.camel.component.file.GenericFileHelper;
import org.apache.camel.component.file.GenericFileOperationFailedException;
import org.apache.camel.support.ObjectHelper;
import org.apache.camel.support.task.BlockingTask;
@@ -525,9 +526,16 @@ public class FtpOperations implements
RemoteFileOperations<FTPFile> {
"Exchange should have the " +
FileComponent.FILE_EXCHANGE_FILE + " set");
String relativeName = target.getRelativeFilePath();
+ File localWorkDir = local;
temp = new File(local, relativeName + ".inprogress");
local = new File(local, relativeName);
+ // ensure the local work file stays within the local work
directory (CAMEL-23765)
+ if (endpoint.isJailStartingDirectory()) {
+ GenericFileHelper.jailToLocalWorkDirectory(temp, localWorkDir);
+ GenericFileHelper.jailToLocalWorkDirectory(local,
localWorkDir);
+ }
+
// create directory to local work file
boolean result = local.mkdirs();
if (!result) {
diff --git
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
index 76b40e95de38..f661f8c9ea48 100644
---
a/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
+++
b/components/camel-ftp/src/main/java/org/apache/camel/component/file/remote/SftpOperations.java
@@ -55,6 +55,7 @@ import org.apache.camel.component.file.FileComponent;
import org.apache.camel.component.file.GenericFile;
import org.apache.camel.component.file.GenericFileEndpoint;
import org.apache.camel.component.file.GenericFileExist;
+import org.apache.camel.component.file.GenericFileHelper;
import org.apache.camel.component.file.GenericFileOperationFailedException;
import org.apache.camel.spi.CamelLogger;
import org.apache.camel.support.ResourceHelper;
@@ -935,9 +936,16 @@ public class SftpOperations implements
RemoteFileOperations<SftpRemoteFile> {
// use relative filename in local work directory
String relativeName = file.getRelativeFilePath();
+ File localWorkDir = local;
temp = new File(local, relativeName + ".inprogress");
local = new File(local, relativeName);
+ // ensure the local work file stays within the local work
directory (CAMEL-23765)
+ if (endpoint.isJailStartingDirectory()) {
+ GenericFileHelper.jailToLocalWorkDirectory(temp, localWorkDir);
+ GenericFileHelper.jailToLocalWorkDirectory(local,
localWorkDir);
+ }
+
// create directory to local work file
local.mkdirs();
diff --git
a/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
b/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
index ba038a897233..5b37affa2ef0 100644
---
a/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
+++
b/components/camel-smb/src/main/java/org/apache/camel/component/smb/SmbOperations.java
@@ -44,6 +44,7 @@ import org.apache.camel.component.file.FileComponent;
import org.apache.camel.component.file.GenericFile;
import org.apache.camel.component.file.GenericFileEndpoint;
import org.apache.camel.component.file.GenericFileExist;
+import org.apache.camel.component.file.GenericFileHelper;
import org.apache.camel.component.file.GenericFileOperationFailedException;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.IOHelper;
@@ -291,11 +292,18 @@ public class SmbOperations implements SmbFileOperations {
// use relative filename in local work directory
String relativeName = file.getRelativeFilePath();
+ java.io.File localWorkDir = local;
temp = new java.io.File(local, relativeName + ".inprogress");
+ local = new java.io.File(local, relativeName);
+
+ // ensure the local work file stays within the local work
directory (CAMEL-23765)
+ if (endpoint.isJailStartingDirectory()) {
+ GenericFileHelper.jailToLocalWorkDirectory(temp, localWorkDir);
+ GenericFileHelper.jailToLocalWorkDirectory(local,
localWorkDir);
+ }
// create directory to local work file
- local.mkdirs();
- local = new java.io.File(local, relativeName);
+ localWorkDir.mkdirs();
// delete any existing files
if (temp.exists()) {