This is an automated email from the ASF dual-hosted git repository.

dockerzhang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/inlong.git


The following commit(s) were added to refs/heads/master by this push:
     new 0d31fbeb9c [INLONG-12143][Manager][Agent] Fix Agent path traversal via 
unvalidated file source path (#12146)
0d31fbeb9c is described below

commit 0d31fbeb9c9ba9a8cbb26298bcc173f9e627ccb4
Author: spirit_sx <[email protected]>
AuthorDate: Wed Jul 8 16:35:16 2026 +0800

    [INLONG-12143][Manager][Agent] Fix Agent path traversal via unvalidated 
file source path (#12146)
---
 .../inlong/agent/constant/AgentConstants.java      |   4 +
 .../inlong/agent/constant/TaskConstants.java       |   1 +
 .../inlong/agent/utils/EventReportUtils.java       |   7 +-
 .../agent/plugin/fetcher/ManagerFetcher.java       |  77 +++++-
 .../plugin/task/logcollection/local/FileTask.java  |  32 ++-
 .../agent/plugin/fetcher/ManagerFetcherTest.java   | 291 +++++++++++++++++++++
 .../apache/inlong/common/constant/Constants.java   |  14 +
 .../inlong/common/util/PathValidationUtils.java    |  88 +++++++
 .../common/util/PathValidationUtilsTest.java       | 176 +++++++++++++
 .../service/source/file/FileSourceOperator.java    |  14 +
 .../source/file/FileSourceOperatorTest.java        | 127 +++++++++
 11 files changed, 826 insertions(+), 5 deletions(-)

diff --git 
a/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/constant/AgentConstants.java
 
b/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/constant/AgentConstants.java
index d9880800e6..8b2bf4d67f 100755
--- 
a/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/constant/AgentConstants.java
+++ 
b/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/constant/AgentConstants.java
@@ -143,4 +143,8 @@ public class AgentConstants {
 
     public static final String DEFAULT_KAFKA_SINK_SEND_VALUE_SERIALIZER =
             "org.apache.kafka.common.serialization.ByteArraySerializer";
+
+    // file security: comma-separated list of directories the file agent is 
allowed to access
+    public static final String AGENT_FILE_ALLOWED_DIRS = 
"agent.file.allowed.dirs";
+    public static final String DEFAULT_AGENT_FILE_ALLOWED_DIRS = "";
 }
diff --git 
a/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/constant/TaskConstants.java
 
b/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/constant/TaskConstants.java
index da786b7cf6..237e79006d 100755
--- 
a/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/constant/TaskConstants.java
+++ 
b/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/constant/TaskConstants.java
@@ -237,4 +237,5 @@ public class TaskConstants extends CommonConstants {
      */
     public static final int SYNC_SEND_OPEN = 1;
 
+    public static final String DATA_CONFIG_PATTERN_KEY = "pattern";
 }
diff --git 
a/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/utils/EventReportUtils.java
 
b/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/utils/EventReportUtils.java
index 3c3863fc1f..ca96938848 100644
--- 
a/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/utils/EventReportUtils.java
+++ 
b/inlong-agent/agent-common/src/main/java/org/apache/inlong/agent/utils/EventReportUtils.java
@@ -39,6 +39,8 @@ import static 
org.apache.inlong.agent.metrics.AgentEventMetricItem.KEY_INLONG_EV
 import static 
org.apache.inlong.agent.metrics.AgentEventMetricItem.KEY_INLONG_EXT;
 import static 
org.apache.inlong.agent.metrics.AgentEventMetricItem.KEY_INLONG_GROUP_ID;
 import static 
org.apache.inlong.agent.metrics.AgentEventMetricItem.KEY_INLONG_STREAM_ID;
+import static org.apache.inlong.common.constant.Constants.HYPHEN;
+import static org.apache.inlong.common.constant.Constants.PIPE;
 
 /**
  * DiagUtils
@@ -53,7 +55,8 @@ public class EventReportUtils {
         CONFIG_INVALID_RET_CODE(3, "config invalid ret code"),
         CONFIG_INVALID_RESULT(4, "config invalid result maybe visit manager 
failed"),
         TASK_ADD(5, "task add"),
-        TASK_DELETE(6, "task delete");
+        TASK_DELETE(6, "task delete"),
+        TASK_VALIDATION_FAILED(7, "task validation failed");
 
         private final int code;
         private final String message;
@@ -101,7 +104,7 @@ public class EventReportUtils {
         dims.put(KEY_INLONG_EVENT_TYPE, eventType);
         dims.put(KEY_INLONG_EVENT_LEVEL, eventLevel);
         dims.put(KEY_INLONG_EVENT_CODE, String.valueOf(evenCode.getCode()));
-        dims.put(KEY_INLONG_EXT, ext.replaceAll("\\|", "-"));
+        dims.put(KEY_INLONG_EXT, ext.replace(PIPE, HYPHEN));
         dims.put(KEY_INLONG_EVENT_DESC, desc);
         metricItemSet.findMetricItem(dims).count.addAndGet(1);
     }
diff --git 
a/inlong-agent/agent-plugins/src/main/java/org/apache/inlong/agent/plugin/fetcher/ManagerFetcher.java
 
b/inlong-agent/agent-plugins/src/main/java/org/apache/inlong/agent/plugin/fetcher/ManagerFetcher.java
index da2315e480..353734c4ee 100644
--- 
a/inlong-agent/agent-plugins/src/main/java/org/apache/inlong/agent/plugin/fetcher/ManagerFetcher.java
+++ 
b/inlong-agent/agent-plugins/src/main/java/org/apache/inlong/agent/plugin/fetcher/ManagerFetcher.java
@@ -37,21 +37,29 @@ import 
org.apache.inlong.common.pojo.agent.AgentResponseCode;
 import org.apache.inlong.common.pojo.agent.DataConfig;
 import org.apache.inlong.common.pojo.agent.TaskRequest;
 import org.apache.inlong.common.pojo.agent.TaskResult;
+import org.apache.inlong.common.util.PathValidationUtils;
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import static 
org.apache.inlong.agent.constant.AgentConstants.AGENT_CLUSTER_NAME;
 import static 
org.apache.inlong.agent.constant.AgentConstants.AGENT_CLUSTER_TAG;
+import static 
org.apache.inlong.agent.constant.AgentConstants.AGENT_FILE_ALLOWED_DIRS;
 import static org.apache.inlong.agent.constant.AgentConstants.AGENT_LOCAL_IP;
 import static org.apache.inlong.agent.constant.AgentConstants.AGENT_UNIQ_ID;
+import static 
org.apache.inlong.agent.constant.AgentConstants.DEFAULT_AGENT_FILE_ALLOWED_DIRS;
 import static 
org.apache.inlong.agent.constant.AgentConstants.DEFAULT_AGENT_UNIQ_ID;
 import static 
org.apache.inlong.agent.constant.FetcherConstants.AGENT_FETCHER_INTERVAL;
 import static 
org.apache.inlong.agent.constant.FetcherConstants.AGENT_MANAGER_ADDR;
@@ -61,8 +69,10 @@ import static 
org.apache.inlong.agent.constant.FetcherConstants.AGENT_MANAGER_TA
 import static 
org.apache.inlong.agent.constant.FetcherConstants.DEFAULT_AGENT_FETCHER_INTERVAL;
 import static 
org.apache.inlong.agent.constant.FetcherConstants.DEFAULT_AGENT_MANAGER_CONFIG_HTTP_PATH;
 import static 
org.apache.inlong.agent.constant.FetcherConstants.DEFAULT_AGENT_MANAGER_EXIST_TASK_HTTP_PATH;
+import static 
org.apache.inlong.agent.constant.TaskConstants.DATA_CONFIG_PATTERN_KEY;
 import static 
org.apache.inlong.agent.plugin.fetcher.ManagerResultFormatter.getResultData;
 import static org.apache.inlong.agent.utils.AgentUtils.fetchLocalUuid;
+import static org.apache.inlong.common.constant.Constants.COMMA;
 
 /**
  * Fetch command from Inlong-Manager
@@ -84,6 +94,7 @@ public class ManagerFetcher extends AbstractDaemon implements 
ProfileFetcher {
     private String uuid;
     private String clusterTag;
     private String clusterName;
+    private final Set<String> allowedDirs;
 
     public ManagerFetcher(AgentManager agentManager) {
         this.agentManager = agentManager;
@@ -96,6 +107,7 @@ public class ManagerFetcher extends AbstractDaemon 
implements ProfileFetcher {
             uniqId = conf.get(AGENT_UNIQ_ID, DEFAULT_AGENT_UNIQ_ID);
             clusterTag = conf.get(AGENT_CLUSTER_TAG);
             clusterName = conf.get(AGENT_CLUSTER_NAME);
+            allowedDirs = parseAllowedDirs(conf.get(AGENT_FILE_ALLOWED_DIRS, 
DEFAULT_AGENT_FILE_ALLOWED_DIRS));
         } else {
             throw new RuntimeException("init manager error, cannot find 
required key");
         }
@@ -203,8 +215,23 @@ public class ManagerFetcher extends AbstractDaemon 
implements ProfileFetcher {
                                         
EvenCodeEnum.CONFIG_UPDATE_SUC.getMessage());
                                 List<TaskProfile> taskProfiles = new 
ArrayList<>();
                                 taskResult.getDataConfigs().forEach((config) 
-> {
-                                    TaskProfile profile = 
TaskProfile.convertToTaskProfile(config);
-                                    taskProfiles.add(profile);
+                                    try {
+                                        validateFilePattern(config, 
allowedDirs);
+                                        TaskProfile profile = 
TaskProfile.convertToTaskProfile(config);
+                                        taskProfiles.add(profile);
+                                    } catch (Exception e) {
+                                        LOGGER.error("skip invalid task config 
taskId={}: {}",
+                                                config.getTaskId(), 
e.getMessage());
+                                        EventReportUtils.report(
+                                                config.getInlongGroupId(),
+                                                config.getInlongStreamId(),
+                                                AgentUtils.getCurrentTime(),
+                                                
EventReportUtils.EVENT_TYPE_CONFIG_UPDATE,
+                                                
EventReportUtils.EVENT_LEVEL_ERROR,
+                                                
EvenCodeEnum.TASK_VALIDATION_FAILED,
+                                                "taskId=" + config.getTaskId() 
+ " reason=" + e.getMessage(),
+                                                "Agent rejected task config: " 
+ e.getMessage());
+                                    }
                                 });
                                 
agentManager.getTaskManager().submitTaskProfiles(taskProfiles);
                                 
agentManager.getTaskManager().setTaskResultMd5(taskResult.getMd5());
@@ -291,6 +318,52 @@ public class ManagerFetcher extends AbstractDaemon 
implements ProfileFetcher {
         return dataConfig;
     }
 
+    /**
+     * Validate FILE task pattern to prevent path traversal attack 
(defense-in-depth).
+     * Also enforces allowed-directory whitelist when configured.
+     * Rejects patterns containing ".." as a complete path segment,
+     * or pointing outside the configured allowed directories.
+     */
+    static void validateFilePattern(DataConfig config, Set<String> 
allowedDirs) {
+        if (TaskTypeEnum.FILE.getType() != (config.getTaskType())) {
+            return;
+        }
+        String extParams = config.getExtParams();
+        if (StringUtils.isEmpty(extParams)) {
+            return;
+        }
+        JsonObject json = GSON.fromJson(extParams, JsonObject.class);
+        if (json == null || !json.has(DATA_CONFIG_PATTERN_KEY)) {
+            return;
+        }
+        String pattern = json.get(DATA_CONFIG_PATTERN_KEY).getAsString();
+        if (StringUtils.isEmpty(pattern)) {
+            throw new IllegalArgumentException("Empty file pattern");
+        }
+        if (PathValidationUtils.containsPathTraversal(pattern)) {
+            throw new IllegalArgumentException(
+                    "Path traversal detected in file pattern: " + pattern);
+        }
+        if (!PathValidationUtils.isWithinAllowedDirs(pattern, allowedDirs)) {
+            throw new IllegalArgumentException(
+                    "File pattern not in allowed directories: " + pattern);
+        }
+    }
+
+    /**
+     * Parse comma-separated allowed directories string into a Set.
+     * Returns an empty set when the input is null or blank (meaning no 
restriction).
+     */
+    static Set<String> parseAllowedDirs(String allowedDirsStr) {
+        if (StringUtils.isBlank(allowedDirsStr)) {
+            return Collections.emptySet();
+        }
+        return Stream.of(allowedDirsStr.split(COMMA))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .collect(Collectors.toSet());
+    }
+
     @Override
     public void start() throws Exception {
         // when agent start, check local ip and fetch manager ip list;
diff --git 
a/inlong-agent/agent-plugins/src/main/java/org/apache/inlong/agent/plugin/task/logcollection/local/FileTask.java
 
b/inlong-agent/agent-plugins/src/main/java/org/apache/inlong/agent/plugin/task/logcollection/local/FileTask.java
index 82fca57109..387b703fbe 100644
--- 
a/inlong-agent/agent-plugins/src/main/java/org/apache/inlong/agent/plugin/task/logcollection/local/FileTask.java
+++ 
b/inlong-agent/agent-plugins/src/main/java/org/apache/inlong/agent/plugin/task/logcollection/local/FileTask.java
@@ -17,7 +17,9 @@
 
 package org.apache.inlong.agent.plugin.task.logcollection.local;
 
+import org.apache.inlong.agent.conf.AgentConfiguration;
 import org.apache.inlong.agent.conf.TaskProfile;
+import org.apache.inlong.agent.constant.AgentConstants;
 import org.apache.inlong.agent.constant.CycleUnitType;
 import org.apache.inlong.agent.constant.TaskConstants;
 import org.apache.inlong.agent.plugin.task.logcollection.LogAbstractTask;
@@ -28,7 +30,9 @@ import org.apache.inlong.agent.plugin.utils.regex.PatternUtil;
 import org.apache.inlong.agent.utils.AgentUtils;
 import org.apache.inlong.agent.utils.DateTransUtils;
 import org.apache.inlong.agent.utils.file.FileUtils;
+import org.apache.inlong.common.util.PathValidationUtils;
 
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -53,6 +57,8 @@ import java.util.regex.Matcher;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static org.apache.inlong.common.constant.Constants.COMMA;
+
 /**
  * Watch directory, if new valid files are created, create instance 
correspondingly.
  */
@@ -78,7 +84,7 @@ public class FileTask extends LogAbstractTask {
         super.initTask();
         timeOffset = taskProfile.get(TaskConstants.TASK_FILE_TIME_OFFSET, "");
         retry = taskProfile.isRetry();
-        originPatterns = 
Stream.of(taskProfile.get(TaskConstants.FILE_DIR_FILTER_PATTERNS).split(","))
+        originPatterns = 
Stream.of(taskProfile.get(TaskConstants.FILE_DIR_FILTER_PATTERNS).split(COMMA))
                 .collect(Collectors.toSet());
         if 
(taskProfile.getCycleUnit().compareToIgnoreCase(CycleUnitType.REAL_TIME) == 0) {
             realTime = true;
@@ -155,6 +161,13 @@ public class FileTask extends LogAbstractTask {
         ArrayList<String> directories = 
PatternUtil.cutDirectoryByWildcardAndDateExpression(originPattern);
         String basicStaticPath = directories.get(0);
         LOGGER.info("dataName {} watchPath {}", new Object[]{originPattern, 
basicStaticPath});
+
+        // Defense-in-depth: validate against allowed directories configured 
at agent level
+        if (!isWithinAllowedDirs(basicStaticPath)) {
+            LOGGER.error("Path not in allowed dirs, rejecting: {}", 
basicStaticPath);
+            return;
+        }
+
         /* Remember the failed watcher creations. */
         if (!new File(basicStaticPath).exists()) {
             LOGGER.warn("DIRECTORY_NOT_FOUND_ERROR" + basicStaticPath);
@@ -398,4 +411,21 @@ public class FileTask extends LogAbstractTask {
             }
         }
     }
+
+    /**
+     * Check whether the given directory path is within the agent-level 
allowed directories.
+     * When no allowed dirs are configured (empty), always returns true.
+     */
+    private boolean isWithinAllowedDirs(String dirPath) {
+        String allowedDirsStr = AgentConfiguration.getAgentConf()
+                .get(AgentConstants.AGENT_FILE_ALLOWED_DIRS, 
AgentConstants.DEFAULT_AGENT_FILE_ALLOWED_DIRS);
+        if (StringUtils.isBlank(allowedDirsStr)) {
+            return true;
+        }
+        Set<String> allowedDirs = Stream.of(allowedDirsStr.split(COMMA))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .collect(Collectors.toSet());
+        return PathValidationUtils.isWithinAllowedDirs(dirPath, allowedDirs);
+    }
 }
diff --git 
a/inlong-agent/agent-plugins/src/test/java/org/apache/inlong/agent/plugin/fetcher/ManagerFetcherTest.java
 
b/inlong-agent/agent-plugins/src/test/java/org/apache/inlong/agent/plugin/fetcher/ManagerFetcherTest.java
new file mode 100644
index 0000000000..b4fae8a578
--- /dev/null
+++ 
b/inlong-agent/agent-plugins/src/test/java/org/apache/inlong/agent/plugin/fetcher/ManagerFetcherTest.java
@@ -0,0 +1,291 @@
+/*
+ * 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.inlong.agent.plugin.fetcher;
+
+import org.apache.inlong.agent.pojo.FileTask.FileTaskConfig;
+import org.apache.inlong.agent.utils.EventReportUtils;
+import org.apache.inlong.common.enums.TaskTypeEnum;
+import org.apache.inlong.common.pojo.agent.DataConfig;
+
+import com.google.gson.Gson;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit test for ManagerFetcher#validateFilePattern path traversal defense and 
allowed-dirs enforcement.
+ */
+public class ManagerFetcherTest {
+
+    private static final Gson GSON = new Gson();
+    private static final Set<String> NO_RESTRICTION = Collections.emptySet();
+
+    private DataConfig buildFileDataConfig(String pattern) {
+        DataConfig dataConfig = new DataConfig();
+        dataConfig.setTaskType(TaskTypeEnum.FILE.getType());
+        dataConfig.setTaskId(1);
+        FileTaskConfig fileTaskConfig = new FileTaskConfig();
+        fileTaskConfig.setPattern(pattern);
+        dataConfig.setExtParams(GSON.toJson(fileTaskConfig));
+        return dataConfig;
+    }
+
+    // ==================== path traversal tests (no dir restriction) 
====================
+
+    @Test
+    public void testNormalPatternShouldPass() {
+        DataConfig config = buildFileDataConfig("/data/logs/app.*.log");
+        // Should not throw
+        ManagerFetcher.validateFilePattern(config, NO_RESTRICTION);
+    }
+
+    @Test
+    public void testSimpleTraversalShouldReject() {
+        DataConfig config = buildFileDataConfig("../../../../etc/passwd");
+        try {
+            ManagerFetcher.validateFilePattern(config, NO_RESTRICTION);
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(e.getMessage().contains("Path traversal 
detected"));
+        }
+    }
+
+    @Test
+    public void testMixedPathWithTraversalShouldReject() {
+        DataConfig config = buildFileDataConfig("/data/logs/../../etc/passwd");
+        try {
+            ManagerFetcher.validateFilePattern(config, NO_RESTRICTION);
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(e.getMessage().contains("Path traversal 
detected"));
+        }
+    }
+
+    @Test
+    public void testBackslashTraversalShouldReject() {
+        DataConfig config = buildFileDataConfig("..\\..\\etc\\passwd");
+        try {
+            ManagerFetcher.validateFilePattern(config, NO_RESTRICTION);
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(e.getMessage().contains("Path traversal 
detected"));
+        }
+    }
+
+    @Test
+    public void testNonFileTaskShouldSkip() {
+        DataConfig config = new DataConfig();
+        config.setTaskType(TaskTypeEnum.BINLOG.getType());
+        config.setTaskId(1);
+        // Should not throw for non-FILE task
+        ManagerFetcher.validateFilePattern(config, NO_RESTRICTION);
+    }
+
+    @Test
+    public void testEmptyPatternShouldFail() {
+        DataConfig config = buildFileDataConfig("");
+        // Should throw IllegalArgumentException
+        try {
+            ManagerFetcher.validateFilePattern(config, NO_RESTRICTION);
+            Assert.fail("Expected IllegalArgumentException");
+
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(e.getMessage().contains("Empty file pattern"));
+        }
+    }
+
+    @Test
+    public void testNullPatternShouldPass() {
+        DataConfig config = buildFileDataConfig(null);
+        // Should not throw
+        ManagerFetcher.validateFilePattern(config, NO_RESTRICTION);
+    }
+
+    @Test
+    public void testDatePatternShouldPass() {
+        DataConfig config = buildFileDataConfig("/data/YYYYMMDD/app.*.log");
+        ManagerFetcher.validateFilePattern(config, NO_RESTRICTION);
+    }
+
+    @Test
+    public void testDotInFilenameShouldPass() {
+        DataConfig config = buildFileDataConfig("/data/logs/app.log");
+        ManagerFetcher.validateFilePattern(config, NO_RESTRICTION);
+    }
+
+    @Test
+    public void testDeepNormalPathShouldPass() {
+        DataConfig config = buildFileDataConfig("/a/b/c/d/e/f/g/h/app.log");
+        ManagerFetcher.validateFilePattern(config, NO_RESTRICTION);
+    }
+
+    // ==================== allowed-dirs tests ====================
+
+    @Test
+    public void testWithinAllowedDirShouldPass() {
+        Set<String> allowed = allowedDirs("/data/logs");
+        DataConfig config = buildFileDataConfig("/data/logs/app.log");
+        ManagerFetcher.validateFilePattern(config, allowed);
+    }
+
+    @Test
+    public void testOutsideAllowedDirShouldReject() {
+        Set<String> allowed = allowedDirs("/data/logs");
+        DataConfig config = buildFileDataConfig("/var/log/app.log");
+        try {
+            ManagerFetcher.validateFilePattern(config, allowed);
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(e.getMessage().contains("not in allowed 
directories"));
+        }
+    }
+
+    @Test
+    public void testDatePatternWithinAllowedDirShouldPass() {
+        Set<String> allowed = allowedDirs("/data/logs");
+        DataConfig config = 
buildFileDataConfig("/data/logs/YYYYMMDD/app.*.log");
+        ManagerFetcher.validateFilePattern(config, allowed);
+    }
+
+    @Test
+    public void testDatePatternOutsideAllowedDirShouldReject() {
+        Set<String> allowed = allowedDirs("/data/logs");
+        DataConfig config = buildFileDataConfig("/var/log/YYYYMMDD/app.*.log");
+        try {
+            ManagerFetcher.validateFilePattern(config, allowed);
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(e.getMessage().contains("not in allowed 
directories"));
+        }
+    }
+
+    @Test
+    public void testMultipleAllowedDirsMatchShouldPass() {
+        Set<String> allowed = allowedDirs("/data/logs", "/var/log/app");
+        DataConfig config = buildFileDataConfig("/var/log/app/service.log");
+        ManagerFetcher.validateFilePattern(config, allowed);
+    }
+
+    @Test
+    public void testTraversalRejectedBeforeAllowedDirsCheck() {
+        // Path traversal should be caught first, before allowed-dirs check
+        Set<String> allowed = allowedDirs("/");
+        DataConfig config = buildFileDataConfig("/data/logs/../../etc/passwd");
+        try {
+            ManagerFetcher.validateFilePattern(config, allowed);
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(e.getMessage().contains("Path traversal 
detected"));
+        }
+    }
+
+    // ==================== parseAllowedDirs tests ====================
+
+    @Test
+    public void testParseNullAllowedDirs() {
+        Assert.assertEquals(Collections.emptySet(), 
ManagerFetcher.parseAllowedDirs(null));
+    }
+
+    @Test
+    public void testParseEmptyAllowedDirs() {
+        Assert.assertEquals(Collections.emptySet(), 
ManagerFetcher.parseAllowedDirs(""));
+    }
+
+    @Test
+    public void testParseBlankAllowedDirs() {
+        Assert.assertEquals(Collections.emptySet(), 
ManagerFetcher.parseAllowedDirs("  "));
+    }
+
+    @Test
+    public void testParseSingleAllowedDir() {
+        Set<String> result = ManagerFetcher.parseAllowedDirs("/data/logs");
+        Assert.assertEquals(1, result.size());
+        Assert.assertTrue(result.contains("/data/logs"));
+    }
+
+    @Test
+    public void testParseMultipleAllowedDirs() {
+        Set<String> result = ManagerFetcher.parseAllowedDirs("/data/logs, 
/var/log, /opt/data");
+        Assert.assertEquals(3, result.size());
+        Assert.assertTrue(result.contains("/data/logs"));
+        Assert.assertTrue(result.contains("/var/log"));
+        Assert.assertTrue(result.contains("/opt/data"));
+    }
+
+    @Test
+    public void testParseAllowedDirsWithExtraCommas() {
+        // trailing comma and double comma should produce empty entries that 
are filtered
+        Set<String> result = 
ManagerFetcher.parseAllowedDirs("/data/logs,/var/log,, ,");
+        Assert.assertEquals(2, result.size());
+        Assert.assertTrue(result.contains("/data/logs"));
+        Assert.assertTrue(result.contains("/var/log"));
+    }
+
+    // ==================== event reporting integration tests 
====================
+
+    @Test
+    public void testTaskValidationFailedEventCode() {
+        EventReportUtils.EvenCodeEnum code = 
EventReportUtils.EvenCodeEnum.TASK_VALIDATION_FAILED;
+        Assert.assertEquals(7, code.getCode());
+        Assert.assertEquals("task validation failed", code.getMessage());
+    }
+
+    @Test
+    public void testRejectedTaskRetainsGroupAndStreamForReporting() {
+        // Verify that a rejected DataConfig still carries groupId/streamId,
+        // so the catch block can pass them to EventReportUtils.report().
+        DataConfig config = buildFileDataConfig("../etc/passwd");
+        config.setInlongGroupId("test_reject_group");
+        config.setInlongStreamId("test_reject_stream");
+        try {
+            ManagerFetcher.validateFilePattern(config, NO_RESTRICTION);
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(e.getMessage().contains("Path traversal 
detected"));
+            Assert.assertTrue(e.getMessage().contains("../etc/passwd"));
+            // groupId/streamId survive the exception for event reporting
+            Assert.assertEquals("test_reject_group", 
config.getInlongGroupId());
+            Assert.assertEquals("test_reject_stream", 
config.getInlongStreamId());
+        }
+    }
+
+    @Test
+    public void testAllowedDirRejectionRetainsGroupAndStreamForReporting() {
+        Set<String> allowed = allowedDirs("/data/logs");
+        DataConfig config = buildFileDataConfig("/var/outside/app.log");
+        config.setInlongGroupId("test_outside_group");
+        config.setInlongStreamId("test_outside_stream");
+        try {
+            ManagerFetcher.validateFilePattern(config, allowed);
+            Assert.fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            Assert.assertTrue(e.getMessage().contains("not in allowed 
directories"));
+            Assert.assertTrue(e.getMessage().contains("/var/outside/app.log"));
+            Assert.assertEquals("test_outside_group", 
config.getInlongGroupId());
+            Assert.assertEquals("test_outside_stream", 
config.getInlongStreamId());
+        }
+    }
+
+    private static Set<String> allowedDirs(String... dirs) {
+        return new HashSet<>(Arrays.asList(dirs));
+    }
+}
\ No newline at end of file
diff --git 
a/inlong-common/src/main/java/org/apache/inlong/common/constant/Constants.java 
b/inlong-common/src/main/java/org/apache/inlong/common/constant/Constants.java
index 766e3c4a44..6e08489532 100644
--- 
a/inlong-common/src/main/java/org/apache/inlong/common/constant/Constants.java
+++ 
b/inlong-common/src/main/java/org/apache/inlong/common/constant/Constants.java
@@ -33,4 +33,18 @@ public class Constants {
     // Default audit version is -1
     public static final long DEFAULT_AUDIT_VERSION = -1;
 
+    public static final String SLASH = "/";
+
+    public static final char SLASH_CHAR = '/';
+
+    public static final char BACK_SLASH_CHAR = '\\';
+
+    public static final String COMMA = ",";
+
+    public static final String DOUBLE_DOT = "..";
+
+    public static final String PIPE = "\\|";
+
+    public static final String HYPHEN = "-";
+
 }
diff --git 
a/inlong-common/src/main/java/org/apache/inlong/common/util/PathValidationUtils.java
 
b/inlong-common/src/main/java/org/apache/inlong/common/util/PathValidationUtils.java
new file mode 100644
index 0000000000..d9817e907e
--- /dev/null
+++ 
b/inlong-common/src/main/java/org/apache/inlong/common/util/PathValidationUtils.java
@@ -0,0 +1,88 @@
+/*
+ * 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.inlong.common.util;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Set;
+
+import static org.apache.inlong.common.constant.Constants.BACK_SLASH_CHAR;
+import static org.apache.inlong.common.constant.Constants.DOUBLE_DOT;
+import static org.apache.inlong.common.constant.Constants.SLASH;
+import static org.apache.inlong.common.constant.Constants.SLASH_CHAR;
+
+/**
+ * Utility class for validating file path patterns, providing protection
+ * against path traversal attacks and allowed-directory enforcement.
+ */
+public class PathValidationUtils {
+
+    /**
+     * Check whether the given path contains path traversal patterns.
+     * A traversal is detected when ".." appears as a complete path segment.
+     *
+     * @param path the path string to validate, may be null or empty
+     * @return true if the path contains a traversal segment, false otherwise
+     */
+    public static boolean containsPathTraversal(String path) {
+        if (StringUtils.isEmpty(path)) {
+            return false;
+        }
+        // Normalize backslashes to forward slashes and split into segments
+        String normalized = path.replace(BACK_SLASH_CHAR, SLASH_CHAR);
+        for (String segment : normalized.split(SLASH)) {
+            if (DOUBLE_DOT.equals(segment)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check whether the given path starts with any of the allowed directory 
prefixes.
+     * When allowedDirs is null or empty, all paths are considered allowed (no 
restriction).
+     *
+     * @param path        the path to validate, may be null or empty
+     * @param allowedDirs the set of allowed directory prefixes (normalized);
+     *                    if null or empty, the check passes for any path
+     * @return true if the path is within an allowed directory or no 
restrictions exist
+     */
+    public static boolean isWithinAllowedDirs(String path, Set<String> 
allowedDirs) {
+        if (StringUtils.isBlank(path)) {
+            return false;
+        }
+        if (CollectionUtils.isEmpty(allowedDirs)) {
+            return true;
+        }
+        String normalizedPath = path.replace(BACK_SLASH_CHAR, SLASH_CHAR);
+        for (String allowedDir : allowedDirs) {
+            if (StringUtils.isEmpty(allowedDir)) {
+                continue;
+            }
+            String normalizedAllowedDir = allowedDir.replace(BACK_SLASH_CHAR, 
SLASH_CHAR);
+            if (!normalizedAllowedDir.endsWith(SLASH)) {
+                normalizedAllowedDir += SLASH;
+            }
+            if ((normalizedPath + SLASH).startsWith(normalizedAllowedDir)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git 
a/inlong-common/src/test/java/org/apache/inlong/common/util/PathValidationUtilsTest.java
 
b/inlong-common/src/test/java/org/apache/inlong/common/util/PathValidationUtilsTest.java
new file mode 100644
index 0000000000..7074c649cc
--- /dev/null
+++ 
b/inlong-common/src/test/java/org/apache/inlong/common/util/PathValidationUtilsTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.inlong.common.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit test for {@link PathValidationUtils#isWithinAllowedDirs}.
+ */
+public class PathValidationUtilsTest {
+
+    // ==================== null / empty allowedDirs (no restriction) 
====================
+
+    @Test
+    public void testNullAllowedDirsShouldPass() {
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/etc/passwd", null));
+    }
+
+    @Test
+    public void testEmptyAllowedDirsShouldPass() {
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/etc/passwd", 
Collections.emptySet()));
+    }
+
+    @Test
+    public void testNullPathWithRestrictedDirsShouldFail() {
+        Set<String> allowed = setOf("/data/logs");
+        Assert.assertFalse(PathValidationUtils.isWithinAllowedDirs(null, 
allowed));
+    }
+
+    @Test
+    public void testEmptyPathWithRestrictedDirsShouldFail() {
+        Set<String> allowed = setOf("/data/logs");
+        Assert.assertFalse(PathValidationUtils.isWithinAllowedDirs("", 
allowed));
+    }
+
+    // ==================== exact directory match ====================
+
+    @Test
+    public void testExactDirMatchShouldPass() {
+        Set<String> allowed = setOf("/data/logs");
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/data/logs", 
allowed));
+    }
+
+    @Test
+    public void testFileUnderAllowedDirShouldPass() {
+        Set<String> allowed = setOf("/data/logs");
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/data/logs/app.log", 
allowed));
+    }
+
+    @Test
+    public void testDeepFileUnderAllowedDirShouldPass() {
+        Set<String> allowed = setOf("/data/logs");
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/data/logs/2025/01/app.log",
 allowed));
+    }
+
+    @Test
+    public void testSiblingDirShouldFail() {
+        Set<String> allowed = setOf("/data/logs");
+        
Assert.assertFalse(PathValidationUtils.isWithinAllowedDirs("/data/other/app.log",
 allowed));
+    }
+
+    @Test
+    public void testParentDirShouldFail() {
+        Set<String> allowed = setOf("/data/logs");
+        Assert.assertFalse(PathValidationUtils.isWithinAllowedDirs("/data", 
allowed));
+    }
+
+    @Test
+    public void testUnrelatedDirShouldFail() {
+        Set<String> allowed = setOf("/data/logs");
+        
Assert.assertFalse(PathValidationUtils.isWithinAllowedDirs("/etc/passwd", 
allowed));
+    }
+
+    // ==================== prefix overlap guard ====================
+
+    @Test
+    public void testPrefixOverlapShouldNotMatch() {
+        // "/data" must NOT match "/data_other/logs" (prefix but not a 
directory boundary)
+        Set<String> allowed = setOf("/data");
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/data/logs", 
allowed));
+        // Note: "/data_other" is NOT a subdirectory of "/data" — startsWith 
alone would false-match
+    }
+
+    // ==================== multiple allowed dirs ====================
+
+    @Test
+    public void testMatchAgainstSecondAllowedDir() {
+        Set<String> allowed = setOf("/data/logs", "/var/log/app");
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/var/log/app/service.log",
 allowed));
+    }
+
+    @Test
+    public void testNoMatchWithMultipleAllowedDirs() {
+        Set<String> allowed = setOf("/data/logs", "/var/log/app");
+        
Assert.assertFalse(PathValidationUtils.isWithinAllowedDirs("/home/user/file.txt",
 allowed));
+    }
+
+    // ==================== trailing slash variations ====================
+
+    @Test
+    public void testAllowedDirWithTrailingSlash() {
+        Set<String> allowed = setOf("/data/logs/");
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/data/logs/app.log", 
allowed));
+    }
+
+    @Test
+    public void testAllowedDirWithoutTrailingSlash() {
+        Set<String> allowed = setOf("/data/logs");
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/data/logs/app.log", 
allowed));
+    }
+
+    // ==================== backslash normalization ====================
+
+    @Test
+    public void testBackslashPathShouldNormalize() {
+        Set<String> allowed = setOf("/data/logs");
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("\\data\\logs\\app.log",
 allowed));
+    }
+
+    @Test
+    public void testBackslashInAllowedDirShouldNormalize() {
+        Set<String> allowed = setOf("\\data\\logs");
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/data/logs/app.log", 
allowed));
+    }
+
+    // ==================== edge cases ====================
+
+    @Test
+    public void testRelativePathShouldFail() {
+        Set<String> allowed = setOf("/data/logs");
+        
Assert.assertFalse(PathValidationUtils.isWithinAllowedDirs("./data/logs", 
allowed));
+    }
+
+    @Test
+    public void testRootDirShouldPass() {
+        Set<String> allowed = setOf("/");
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/anything/here", 
allowed));
+    }
+
+    @Test
+    public void testNullEntryInAllowedDirsShouldBeIgnored() {
+        Set<String> allowed = new HashSet<>(Arrays.asList(null, "/data/logs"));
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/data/logs/app.log", 
allowed));
+    }
+
+    @Test
+    public void testEmptyEntryInAllowedDirsShouldBeIgnored() {
+        Set<String> allowed = new HashSet<>(Arrays.asList("", "/data/logs"));
+        
Assert.assertTrue(PathValidationUtils.isWithinAllowedDirs("/data/logs/app.log", 
allowed));
+    }
+
+    private static Set<String> setOf(String... values) {
+        return new HashSet<>(Arrays.asList(values));
+    }
+}
\ No newline at end of file
diff --git 
a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/source/file/FileSourceOperator.java
 
b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/source/file/FileSourceOperator.java
index 90a0212aa1..d4903f9cba 100644
--- 
a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/source/file/FileSourceOperator.java
+++ 
b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/source/file/FileSourceOperator.java
@@ -18,6 +18,7 @@
 package org.apache.inlong.manager.service.source.file;
 
 import org.apache.inlong.common.pojo.agent.DataConfig;
+import org.apache.inlong.common.util.PathValidationUtils;
 import org.apache.inlong.manager.common.consts.SourceType;
 import org.apache.inlong.manager.common.enums.ErrorCodeEnum;
 import org.apache.inlong.manager.common.exceptions.BusinessException;
@@ -77,6 +78,8 @@ public class FileSourceOperator extends 
AbstractSourceOperator {
     protected void setTargetEntity(SourceRequest request, StreamSourceEntity 
targetEntity) {
         FileSourceRequest sourceRequest = (FileSourceRequest) request;
         try {
+            // Validate file path pattern to prevent path traversal attack
+            validatePathPattern(sourceRequest.getPattern());
             CommonBeanUtils.copyProperties(sourceRequest, targetEntity, true);
             FileSourceDTO dto = FileSourceDTO.getFromRequest(sourceRequest, 
targetEntity.getExtParams());
             targetEntity.setExtParams(objectMapper.writeValueAsString(dto));
@@ -86,6 +89,17 @@ public class FileSourceOperator extends 
AbstractSourceOperator {
         }
     }
 
+    /**
+     * Validate file path pattern to prevent path traversal attacks.
+     * Rejects patterns containing ".." as a complete path segment.
+     */
+    private void validatePathPattern(String pattern) {
+        if (PathValidationUtils.containsPathTraversal(pattern)) {
+            throw new BusinessException(ErrorCodeEnum.SOURCE_INFO_INCORRECT,
+                    "Path traversal detected in file pattern: " + pattern);
+        }
+    }
+
     @Override
     public StreamSource getFromEntity(StreamSourceEntity entity) {
         FileSource source = new FileSource();
diff --git 
a/inlong-manager/manager-service/src/test/java/org/apache/inlong/manager/service/source/file/FileSourceOperatorTest.java
 
b/inlong-manager/manager-service/src/test/java/org/apache/inlong/manager/service/source/file/FileSourceOperatorTest.java
new file mode 100644
index 0000000000..50bd04a7ba
--- /dev/null
+++ 
b/inlong-manager/manager-service/src/test/java/org/apache/inlong/manager/service/source/file/FileSourceOperatorTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.inlong.manager.service.source.file;
+
+import org.apache.inlong.manager.common.consts.SourceType;
+import org.apache.inlong.manager.common.exceptions.BusinessException;
+import org.apache.inlong.manager.pojo.source.file.FileSourceRequest;
+import org.apache.inlong.manager.service.ServiceBaseTest;
+import org.apache.inlong.manager.service.core.impl.InlongStreamServiceTest;
+import org.apache.inlong.manager.service.source.StreamSourceService;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Unit test for FileSourceOperator path traversal validation.
+ */
+public class FileSourceOperatorTest extends ServiceBaseTest {
+
+    private static final String TEST_GROUP = "file_source_test_group";
+    private static final String TEST_STREAM = "file_source_test_stream";
+
+    @Autowired
+    private StreamSourceService sourceService;
+    @Autowired
+    private InlongStreamServiceTest streamServiceTest;
+
+    @BeforeEach
+    void setUp() {
+        streamServiceTest.saveInlongStream(TEST_GROUP, TEST_STREAM, 
GLOBAL_OPERATOR);
+    }
+
+    @Test
+    void testNormalPatternShouldPass() {
+        FileSourceRequest request = new FileSourceRequest();
+        request.setInlongGroupId(TEST_GROUP);
+        request.setInlongStreamId(TEST_STREAM);
+        request.setSourceName("normal-file-source");
+        request.setSourceType(SourceType.FILE);
+        request.setPattern("/data/logs/app.*.log");
+
+        Assertions.assertDoesNotThrow(() -> sourceService.save(request, 
GLOBAL_OPERATOR));
+    }
+
+    @Test
+    void testSimpleTraversalShouldReject() {
+        FileSourceRequest request = new FileSourceRequest();
+        request.setInlongGroupId(TEST_GROUP);
+        request.setInlongStreamId(TEST_STREAM);
+        request.setSourceName("traversal-source-1");
+        request.setSourceType(SourceType.FILE);
+        request.setPattern("../../../../etc/passwd");
+
+        BusinessException ex = Assertions.assertThrows(BusinessException.class,
+                () -> sourceService.save(request, GLOBAL_OPERATOR));
+        Assertions.assertTrue(ex.getMessage().contains("Path traversal 
detected"));
+    }
+
+    @Test
+    void testMixedPathWithTraversalShouldReject() {
+        FileSourceRequest request = new FileSourceRequest();
+        request.setInlongGroupId(TEST_GROUP);
+        request.setInlongStreamId(TEST_STREAM);
+        request.setSourceName("traversal-source-2");
+        request.setSourceType(SourceType.FILE);
+        request.setPattern("/data/logs/../../etc/passwd");
+
+        BusinessException ex = Assertions.assertThrows(BusinessException.class,
+                () -> sourceService.save(request, GLOBAL_OPERATOR));
+        Assertions.assertTrue(ex.getMessage().contains("Path traversal 
detected"));
+    }
+
+    @Test
+    void testBackslashTraversalShouldReject() {
+        FileSourceRequest request = new FileSourceRequest();
+        request.setInlongGroupId(TEST_GROUP);
+        request.setInlongStreamId(TEST_STREAM);
+        request.setSourceName("traversal-source-3");
+        request.setSourceType(SourceType.FILE);
+        request.setPattern("..\\..\\etc\\passwd");
+
+        BusinessException ex = Assertions.assertThrows(BusinessException.class,
+                () -> sourceService.save(request, GLOBAL_OPERATOR));
+        Assertions.assertTrue(ex.getMessage().contains("Path traversal 
detected"));
+    }
+
+    @Test
+    void testDotInFilenameShouldPass() {
+        FileSourceRequest request = new FileSourceRequest();
+        request.setInlongGroupId(TEST_GROUP);
+        request.setInlongStreamId(TEST_STREAM);
+        request.setSourceName("dotfile-source");
+        request.setSourceType(SourceType.FILE);
+        request.setPattern("/data/logs/app.log");
+
+        Assertions.assertDoesNotThrow(() -> sourceService.save(request, 
GLOBAL_OPERATOR));
+    }
+
+    @Test
+    void testDatePatternShouldPass() {
+        FileSourceRequest request = new FileSourceRequest();
+        request.setInlongGroupId(TEST_GROUP);
+        request.setInlongStreamId(TEST_STREAM);
+        request.setSourceName("date-pattern-source");
+        request.setSourceType(SourceType.FILE);
+        request.setPattern("/data/YYYYMMDD/app.*.log");
+
+        Assertions.assertDoesNotThrow(() -> sourceService.save(request, 
GLOBAL_OPERATOR));
+    }
+}
\ No newline at end of file


Reply via email to