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

jianbin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 2e8a4721e8 feature: Add http request filter for seata-server (#7485)
2e8a4721e8 is described below

commit 2e8a4721e82c8fc966634d982802d96a2e412982
Author: xiaoyu <[email protected]>
AuthorDate: Tue Jul 22 09:39:12 2025 +0800

    feature: Add http request filter for seata-server (#7485)
---
 changes/en-us/2.x.md                               |   4 +-
 changes/zh-cn/2.x.md                               |   4 +-
 .../org/apache/seata/common/ConfigurationKeys.java |  23 ++
 .../org/apache/seata/common/DefaultValues.java     |  28 +++
 .../apache/seata/common/util/HttpClientUtil.java   |  22 ++
 .../core/exception/HttpRequestFilterException.java |  31 +++
 .../rpc/netty/http/BaseHttpChannelHandler.java     |  12 +
 .../core/rpc/netty/http/Http2HttpHandler.java      |  11 +
 .../core/rpc/netty/http/HttpDispatchHandler.java   |  17 ++
 .../rpc/netty/http/filter/HttpFilterContext.java   |  45 ++++
 .../rpc/netty/http/filter/HttpRequestFilter.java   |  42 ++++
 .../netty/http/filter/HttpRequestFilterChain.java  |  42 ++++
 .../http/filter/HttpRequestFilterManager.java      |  60 +++++
 .../netty/http/filter/HttpRequestParamWrapper.java | 211 ++++++++++++++++++
 .../core/rpc/netty/http/Http2HttpHandlerTest.java  | 242 +++++++++++++++------
 .../rpc/netty/http/HttpDispatchHandlerTest.java    | 119 +++++++++-
 .../http/filter/HttpRequestFilterManagerTest.java  | 127 +++++++++++
 .../http/filter/HttpRequestParamWrapperTest.java   | 116 ++++++++++
 script/config-center/config.txt                    |   4 +
 .../boot/autoconfigure/StarterConstants.java       |   4 +
 .../SeataServerEnvironmentPostProcessor.java       |   3 +
 .../server/filter/ServerHttpFilterProperties.java  |  72 ++++++
 .../filter/ServerHttpFilterPropertiesTest.java     |  53 +++++
 .../seata/server/filter/XSSHttpRequestFilter.java  | 141 ++++++++++++
 .../spring/listener/HttpFilterInitListener.java    |  34 +++
 ...a.core.rpc.netty.http.filter.HttpRequestFilter} |   3 +-
 .../src/main/resources/META-INF/spring.factories   |   3 +-
 server/src/main/resources/application.example.yml  |   6 +
 .../main/resources/application.raft.example.yml    |   6 +
 .../server/controller/ClusterControllerTest.java   |  92 ++++++++
 server/src/test/resources/application.properties   |   4 +-
 31 files changed, 1499 insertions(+), 82 deletions(-)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index 5885951f89..e1b3b324ac 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -20,7 +20,7 @@ Add changes here for all PR submitted to the 2.x branch.
 
 ### feature:
 
-- [[#PR_NO](https://github.com/seata/seata/pull/PR_NO)] support XXX
+- [[#7485](https://github.com/apache/incubator-seata/pull/7485)] Add http 
request filter for seata-server
 
 
 ### bugfix:
@@ -56,6 +56,8 @@ Add changes here for all PR submitted to the 2.x branch.
 <!-- 请确保您的 GitHub ID 在以下列表中 -->
 
 - [slievrly](https://github.com/slievrly)
+- [YvCeung](https://github.com/YvCeung)
 - [xjlgod](https://github.com/xjlgod)
 
+
 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 96c30b0c55..d4b052c678 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -20,7 +20,7 @@
 
 ### feature:
 
-- [[#PR_NO](https://github.com/seata/seata/pull/PR_NO)] 支持 XXX
+- [[#7485](https://github.com/apache/incubator-seata/pull/7485)] 
给seata-server端的http请求添加过滤器
 
 
 ### bugfix:
@@ -55,6 +55,8 @@ Thanks to these contributors for their code commits. Please 
report an unintended
 <!-- Please make sure your Github ID is in the list below -->
 
 - [slievrly](https://github.com/slievrly)
+- [YvCeung](https://github.com/YvCeung)
 - [xjlgod](https://github.com/xjlgod)
 
+
 Also, we receive many valuable issues, questions and advices from our 
community. Thanks for you all.
diff --git 
a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java 
b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java
index 67498b0fd6..f8433e3448 100644
--- a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java
+++ b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java
@@ -1135,6 +1135,29 @@ public interface ConfigurationKeys {
      */
     String SERVER_RAFT_COMPRESSOR = SERVER_RAFT + "compressor";
 
+    /**
+     * The constant SERVER_HTTP.
+     */
+    String SERVER_HTTP = SERVER_PREFIX + "http.";
+
+    String SERVER_HTTP_FILTER_PREFIX = SERVER_HTTP + "filter.";
+
+    /**
+     * The constant SERVER_HTTP_FILTERS_ENABLED.
+     */
+    String SERVER_HTTP_FILTER_ENABLE = SERVER_HTTP_FILTER_PREFIX + "enabled";
+
+    /**
+     * The constant SERVER_HTTP_FILTER_XSS_ENABLED.
+     */
+    String SERVER_HTTP_FILTER_XSS_FILTER_ENABLE = SERVER_HTTP_FILTER_PREFIX + 
"xss.enabled";
+
+    /**
+     * The constant SERVER_HTTP_FILTER_XSS_FILTER_KEYWORDS.
+     *
+     */
+    String SERVER_HTTP_FILTER_XSS_FILTER_KEYWORDS = SERVER_HTTP_FILTER_PREFIX 
+ "xss.keywords";
+
     /**
      * The constant IS_USE_CLOUD_NAMESPACE_PARSING.
      */
diff --git a/common/src/main/java/org/apache/seata/common/DefaultValues.java 
b/common/src/main/java/org/apache/seata/common/DefaultValues.java
index 5865209beb..0b1c33993c 100644
--- a/common/src/main/java/org/apache/seata/common/DefaultValues.java
+++ b/common/src/main/java/org/apache/seata/common/DefaultValues.java
@@ -17,6 +17,8 @@
 package org.apache.seata.common;
 
 import java.time.Duration;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * The interface Default values.
@@ -606,4 +608,30 @@ public interface DefaultValues {
      * The constant DEFAULT_RAFT_SSL_ENABLED.
      */
     boolean DEFAULT_RAFT_SSL_ENABLED = false;
+
+    List<String> DEFAULT_XSS_KEYWORDS = Arrays.asList(
+            "<script>",
+            "</script>",
+            "javascript:",
+            "vbscript:",
+            "data:",
+            "expression(",
+            "onerror",
+            "onload",
+            "onclick",
+            "onmouseover",
+            "onfocus",
+            "onblur",
+            "onmouseenter",
+            "onmouseleave",
+            "onkeydown",
+            "onkeyup",
+            "onchange",
+            "<iframe>",
+            "<img>",
+            "<svg>",
+            "<embed>",
+            "<object>",
+            "<style>",
+            "<link>");
 }
diff --git 
a/common/src/main/java/org/apache/seata/common/util/HttpClientUtil.java 
b/common/src/main/java/org/apache/seata/common/util/HttpClientUtil.java
index b3bb06c2c8..0490ad80f4 100644
--- a/common/src/main/java/org/apache/seata/common/util/HttpClientUtil.java
+++ b/common/src/main/java/org/apache/seata/common/util/HttpClientUtil.java
@@ -172,4 +172,26 @@ public class HttpClientUtil {
         }
         return null;
     }
+
+    public static CloseableHttpResponse doPostJson(
+            String url, String jsonBody, Map<String, String> headers, int 
timeout) throws IOException {
+        RequestConfig requestConfig = RequestConfig.custom()
+                .setSocketTimeout(timeout)
+                .setConnectTimeout(timeout)
+                .build();
+
+        HttpPost post = new HttpPost(url);
+        post.setConfig(requestConfig);
+
+        if (headers != null) {
+            headers.forEach(post::addHeader);
+        }
+        post.setHeader("Content-Type", "application/json");
+
+        StringEntity entity = new StringEntity(jsonBody, 
StandardCharsets.UTF_8);
+        post.setEntity(entity);
+
+        CloseableHttpClient client = HttpClients.createDefault();
+        return client.execute(post);
+    }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/exception/HttpRequestFilterException.java
 
b/core/src/main/java/org/apache/seata/core/exception/HttpRequestFilterException.java
new file mode 100644
index 0000000000..abda72c37c
--- /dev/null
+++ 
b/core/src/main/java/org/apache/seata/core/exception/HttpRequestFilterException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.seata.core.exception;
+
+/**
+ * The type  HttpRequestFilter exception.
+ *
+ */
+public class HttpRequestFilterException extends RuntimeException {
+    public HttpRequestFilterException(String message) {
+        super(message);
+    }
+
+    public HttpRequestFilterException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/BaseHttpChannelHandler.java
 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/BaseHttpChannelHandler.java
index 995b4efb5f..1dccda8e0c 100644
--- 
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/BaseHttpChannelHandler.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/BaseHttpChannelHandler.java
@@ -19,7 +19,11 @@ package org.apache.seata.core.rpc.netty.http;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.netty.channel.SimpleChannelInboundHandler;
 import org.apache.seata.common.thread.NamedThreadFactory;
+import org.apache.seata.core.exception.HttpRequestFilterException;
 import org.apache.seata.core.rpc.netty.NettyServerConfig;
+import org.apache.seata.core.rpc.netty.http.filter.HttpFilterContext;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterChain;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterManager;
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -44,4 +48,12 @@ public abstract class BaseHttpChannelHandler<T> extends 
SimpleChannelInboundHand
     static {
         Runtime.getRuntime().addShutdownHook(new 
Thread(HTTP_HANDLER_THREADS::shutdown));
     }
+
+    /**
+     * The filter has a unified entry point and is called by subclasses at an 
appropriate time
+     */
+    protected final void doFilterInternal(HttpFilterContext<?> context) throws 
HttpRequestFilterException {
+        HttpRequestFilterChain filterChain = 
HttpRequestFilterManager.getFilterChain();
+        filterChain.doFilter(context);
+    }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandler.java 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandler.java
index 5dc3f813a9..feb8deda67 100644
--- 
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandler.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandler.java
@@ -32,6 +32,9 @@ import io.netty.handler.codec.http2.Http2Headers;
 import io.netty.handler.codec.http2.Http2HeadersFrame;
 import io.netty.handler.codec.http2.Http2StreamFrame;
 import org.apache.seata.common.rpc.http.HttpContext;
+import org.apache.seata.core.exception.HttpRequestFilterException;
+import org.apache.seata.core.rpc.netty.http.filter.HttpFilterContext;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestParamWrapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -87,6 +90,11 @@ public class Http2HttpHandler extends 
BaseHttpChannelHandler<Http2StreamFrame> {
             String body = bodyBuffer != null ? 
bodyBuffer.toString(StandardCharsets.UTF_8) : "";
             SimpleHttp2Request request = new SimpleHttp2Request(method, path, 
http2Headers, body);
 
+            // After receiving the complete request, the filtering logic is 
executed
+            HttpFilterContext<SimpleHttp2Request> context =
+                    new HttpFilterContext<>(request, () -> new 
HttpRequestParamWrapper(request));
+            doFilterInternal(context);
+
             // reuse HttpDispatchHandler logic
             boolean keepAlive = true; // In HTTP/2, connections are persistent 
by default
             QueryStringDecoder queryStringDecoder = new 
QueryStringDecoder(request.getPath());
@@ -116,6 +124,9 @@ public class Http2HttpHandler extends 
BaseHttpChannelHandler<Http2StreamFrame> {
             Object[] args = ParameterParser.getArgValues(
                     httpInvocation.getParamMetaData(), handleMethod, 
requestDataNode, httpContext);
             handle(httpController, handleMethod, args, ctx, httpContext);
+        } catch (HttpRequestFilterException e) {
+            LOGGER.warn("Request blocked by filter while processing HTTP2 
request: {}", e.getMessage());
+            sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST);
         } catch (Exception e) {
             LOGGER.error("Exception occurred while processing HTTP2 request: 
{}", e.getMessage(), e);
             sendErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
index 0baa5c47fe..caafc308fd 100644
--- 
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandler.java
@@ -34,6 +34,9 @@ import io.netty.handler.codec.http.multipart.Attribute;
 import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
 import io.netty.handler.codec.http.multipart.InterfaceHttpData;
 import org.apache.seata.common.rpc.http.HttpContext;
+import org.apache.seata.core.exception.HttpRequestFilterException;
+import org.apache.seata.core.rpc.netty.http.filter.HttpFilterContext;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestParamWrapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,6 +52,20 @@ public class HttpDispatchHandler extends 
BaseHttpChannelHandler<HttpRequest> {
 
     @Override
     protected void channelRead0(ChannelHandlerContext ctx, HttpRequest 
httpRequest) {
+        try {
+            HttpFilterContext<HttpRequest> context =
+                    new HttpFilterContext<>(httpRequest, () -> new 
HttpRequestParamWrapper(httpRequest));
+            doFilterInternal(context);
+        } catch (HttpRequestFilterException e) {
+            LOGGER.warn("Request blocked by filter: {}", e.getMessage());
+            sendErrorResponse(ctx, HttpResponseStatus.BAD_REQUEST, false);
+            return;
+        } catch (Exception e) {
+            LOGGER.error("Unexpected error during filter execution: {}", 
e.getMessage(), e);
+            sendErrorResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, 
false);
+            return;
+        }
+
         try {
             boolean keepAlive = HttpUtil.isKeepAlive(httpRequest)
                     && httpRequest.protocolVersion().isKeepAliveDefault();
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpFilterContext.java
 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpFilterContext.java
new file mode 100644
index 0000000000..0fac67cab6
--- /dev/null
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpFilterContext.java
@@ -0,0 +1,45 @@
+/*
+ * 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.seata.core.rpc.netty.http.filter;
+
+import java.util.function.Supplier;
+
+public class HttpFilterContext<T> {
+    private final T request;
+    private final Supplier<HttpRequestParamWrapper> paramWrapperSupplier;
+    private volatile HttpRequestParamWrapper paramWrapper;
+
+    public HttpFilterContext(T request, Supplier<HttpRequestParamWrapper> 
paramWrapperSupplier) {
+        this.request = request;
+        this.paramWrapperSupplier = paramWrapperSupplier;
+    }
+
+    public T getRequest() {
+        return request;
+    }
+
+    public HttpRequestParamWrapper getParamWrapper() {
+        if (paramWrapper == null) {
+            synchronized (this) {
+                if (paramWrapper == null) {
+                    paramWrapper = paramWrapperSupplier.get();
+                }
+            }
+        }
+        return paramWrapper;
+    }
+}
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilter.java
 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilter.java
new file mode 100644
index 0000000000..96728f450e
--- /dev/null
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilter.java
@@ -0,0 +1,42 @@
+/*
+ * 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.seata.core.rpc.netty.http.filter;
+
+import org.apache.seata.core.exception.HttpRequestFilterException;
+
+/**
+ * Interface for Netty HTTP request filters with order and enable control.
+ */
+public interface HttpRequestFilter {
+
+    /**
+     * Filter execution order; lower values run first.
+     */
+    default int getOrder() {
+        return 0;
+    }
+
+    /**
+     * Executes the filter logic.
+     */
+    void doFilter(HttpFilterContext<?> context) throws 
HttpRequestFilterException;
+
+    /**
+     * Determines if the filter should run.
+     */
+    boolean shouldApply();
+}
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterChain.java
 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterChain.java
new file mode 100644
index 0000000000..7fd774e204
--- /dev/null
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterChain.java
@@ -0,0 +1,42 @@
+/*
+ * 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.seata.core.rpc.netty.http.filter;
+
+import org.apache.seata.core.exception.HttpRequestFilterException;
+
+import java.util.List;
+
+public class HttpRequestFilterChain {
+    private final List<HttpRequestFilter> filters;
+
+    public HttpRequestFilterChain(List<HttpRequestFilter> filters) {
+        this.filters = filters;
+    }
+
+    public void doFilter(HttpFilterContext<?> httpFilterContext) throws 
HttpRequestFilterException {
+        for (HttpRequestFilter filter : filters) {
+            filter.doFilter(httpFilterContext);
+        }
+    }
+
+    /**
+     * Get internal filter list (for unit test only).
+     */
+    List<HttpRequestFilter> getFilters() {
+        return filters;
+    }
+}
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterManager.java
 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterManager.java
new file mode 100644
index 0000000000..68b2f7adc1
--- /dev/null
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterManager.java
@@ -0,0 +1,60 @@
+/*
+ * 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.seata.core.rpc.netty.http.filter;
+
+import org.apache.seata.common.loader.EnhancedServiceLoader;
+import org.apache.seata.config.ConfigurationFactory;
+import org.apache.seata.config.ConfigurationKeys;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+public class HttpRequestFilterManager {
+
+    private static final List<HttpRequestFilter> HTTP_REQUEST_FILTERS = new 
ArrayList<>();
+    private static HttpRequestFilterChain HTTP_REQUEST_FILTER_CHAIN;
+
+    private static volatile boolean initialized = false;
+
+    public static synchronized void initializeFilters() {
+        if (initialized) {
+            return;
+        }
+        boolean enableFilter =
+                
ConfigurationFactory.getInstance().getBoolean(ConfigurationKeys.SERVER_HTTP_FILTER_ENABLE,
 true);
+        if (enableFilter) {
+            List<HttpRequestFilter> httpRequestFilters = 
EnhancedServiceLoader.loadAll(HttpRequestFilter.class);
+            for (HttpRequestFilter filter : httpRequestFilters) {
+                if (filter.shouldApply()) {
+                    HTTP_REQUEST_FILTERS.add(filter);
+                }
+            }
+
+            
HTTP_REQUEST_FILTERS.sort(Comparator.comparingInt(HttpRequestFilter::getOrder));
+            HTTP_REQUEST_FILTER_CHAIN = new 
HttpRequestFilterChain(HTTP_REQUEST_FILTERS);
+        }
+        initialized = true;
+    }
+
+    public static HttpRequestFilterChain getFilterChain() {
+        if (!initialized) {
+            throw new IllegalStateException("HttpRequestFilterManager not 
initialized.");
+        }
+        return HTTP_REQUEST_FILTER_CHAIN;
+    }
+}
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestParamWrapper.java
 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestParamWrapper.java
new file mode 100644
index 0000000000..708a60bd25
--- /dev/null
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestParamWrapper.java
@@ -0,0 +1,211 @@
+/*
+ * 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.seata.core.rpc.netty.http.filter;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.QueryStringDecoder;
+import io.netty.handler.codec.http.multipart.Attribute;
+import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
+import io.netty.handler.codec.http.multipart.InterfaceHttpData;
+import io.netty.handler.codec.http2.Http2Headers;
+import org.apache.seata.core.rpc.netty.http.SimpleHttp2Request;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Wrapper for HTTP request parameters from multiple sources: query, form, 
header, JSON body.
+ */
+public class HttpRequestParamWrapper {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(HttpRequestParamWrapper.class);
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    private final Map<String, List<String>> queryParams = new HashMap<>();
+    private final Map<String, List<String>> formParams = new HashMap<>();
+    private final Map<String, List<String>> headerParams = new HashMap<>();
+    private final Map<String, List<String>> jsonParams = new HashMap<>();
+
+    public HttpRequestParamWrapper(HttpRequest httpRequest) {
+        if (!(httpRequest instanceof FullHttpRequest)) {
+            throw new IllegalArgumentException("HttpRequest must be 
FullHttpRequest to read body.");
+        }
+        FullHttpRequest fullRequest = (FullHttpRequest) httpRequest;
+        parseQueryParams(fullRequest);
+        parseHeaders(fullRequest);
+        parseBody(fullRequest);
+    }
+
+    public HttpRequestParamWrapper(SimpleHttp2Request request) {
+        parseQueryParams(request.getPath());
+        parseHeaders(request.getHeaders());
+
+        String contentType = (String) 
request.getHeaders().get(HttpHeaderNames.CONTENT_TYPE);
+        if (contentType == null) {
+            return;
+        }
+
+        try {
+            if (contentType.contains("application/json")) {
+                parseJsonBody(request.getBody());
+            } else if 
(contentType.contains("application/x-www-form-urlencoded")) {
+                parseFormUrlEncodedBody(request.getBody());
+            }
+        } catch (Exception e) {
+            LOGGER.warn("Failed to parse HTTP/2 body: {}", e.getMessage(), e);
+        }
+    }
+
+    private void parseQueryParams(FullHttpRequest request) {
+        QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
+        queryParams.putAll(decoder.parameters());
+    }
+
+    private void parseQueryParams(String path) {
+        QueryStringDecoder decoder = new QueryStringDecoder(path);
+        queryParams.putAll(decoder.parameters());
+    }
+
+    private void parseHeaders(FullHttpRequest request) {
+        for (Map.Entry<String, String> entry : request.headers()) {
+            headerParams.computeIfAbsent(entry.getKey(), k -> new 
ArrayList<>()).add(entry.getValue());
+        }
+    }
+
+    private void parseHeaders(Http2Headers headers) {
+        for (Map.Entry<CharSequence, CharSequence> entry : headers) {
+            headerParams
+                    .computeIfAbsent(entry.getKey().toString(), k -> new 
ArrayList<>())
+                    .add(entry.getValue().toString());
+        }
+    }
+
+    private void parseFormUrlEncodedBody(String body) {
+        if (body == null || body.trim().isEmpty()) {
+            return;
+        }
+        String[] pairs = body.split("&");
+        for (String pair : pairs) {
+            String[] kv = pair.split("=", 2);
+            if (kv.length == 2) {
+                String key = decode(kv[0]);
+                String value = decode(kv[1]);
+                formParams.computeIfAbsent(key, k -> new 
ArrayList<>()).add(value);
+            }
+        }
+    }
+
+    private String decode(String s) {
+        try {
+            return java.net.URLDecoder.decode(s, 
StandardCharsets.UTF_8.name());
+        } catch (Exception e) {
+            LOGGER.warn("Failed to decode form field: {}", s, e);
+            return s;
+        }
+    }
+
+    private void parseBody(FullHttpRequest request) {
+        String contentType = 
request.headers().get(HttpHeaderNames.CONTENT_TYPE);
+        if (contentType == null) {
+            return;
+        }
+
+        ByteBuf originalContent = request.content();
+        ByteBuf copiedBuf = Unpooled.copiedBuffer(originalContent);
+
+        String bodyStr = copiedBuf.toString(StandardCharsets.UTF_8);
+
+        try {
+            if (contentType.contains("application/json")) {
+                parseJsonBody(bodyStr);
+            } else if 
(contentType.contains("application/x-www-form-urlencoded")
+                    || contentType.contains("multipart/form-data")) {
+                // Replace user-controlled URI with constant string during 
internal FullHttpRequest construction for
+                // decoding form parameters.
+                FullHttpRequest copiedRequest = new DefaultFullHttpRequest(
+                        request.protocolVersion(), request.method(), 
"/internal-safe-uri", copiedBuf);
+                parseFormBody(copiedRequest);
+            }
+        } catch (Exception e) {
+            LOGGER.warn("Failed to parse HTTP body: {}", e.getMessage(), e);
+        }
+    }
+
+    private void parseJsonBody(String bodyStr) {
+        try {
+            JsonNode jsonNode = OBJECT_MAPPER.readTree(bodyStr);
+            if (jsonNode != null && jsonNode.isObject()) {
+                jsonNode.fields().forEachRemaining(e -> jsonParams
+                        .computeIfAbsent(e.getKey(), k -> new ArrayList<>())
+                        .add(e.getValue().asText()));
+            }
+        } catch (Exception e) {
+            LOGGER.warn("Failed to parse JSON body: {}", e.getMessage(), e);
+        }
+    }
+
+    private void parseFormBody(FullHttpRequest request) {
+        HttpPostRequestDecoder decoder = null;
+        try {
+            decoder = new HttpPostRequestDecoder(request);
+            for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
+                if (data.getHttpDataType() == 
InterfaceHttpData.HttpDataType.Attribute) {
+                    Attribute attr = (Attribute) data;
+                    formParams
+                            .computeIfAbsent(attr.getName(), k -> new 
ArrayList<>())
+                            .add(attr.getValue());
+                }
+            }
+        } catch (Exception e) {
+            LOGGER.warn("Failed to parse form body: {}", e.getMessage(), e);
+        } finally {
+            if (decoder != null) {
+                decoder.destroy();
+            }
+        }
+    }
+
+    /**
+     * Return all parameters from query, form, header and json, merged into a 
multi-value map.
+     */
+    public Map<String, List<String>> getAllParamsAsMultiMap() {
+        Map<String, List<String>> all = new HashMap<>();
+
+        queryParams.forEach(
+                (k, v) -> all.computeIfAbsent(k, key -> new 
ArrayList<>()).addAll(v));
+        formParams.forEach(
+                (k, v) -> all.computeIfAbsent(k, key -> new 
ArrayList<>()).addAll(v));
+        headerParams.forEach(
+                (k, v) -> all.computeIfAbsent(k, key -> new 
ArrayList<>()).addAll(v));
+        jsonParams.forEach(
+                (k, v) -> all.computeIfAbsent(k, key -> new 
ArrayList<>()).addAll(v));
+
+        return all;
+    }
+}
diff --git 
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandlerTest.java
 
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandlerTest.java
index c4ca1e0dda..7dc00c7919 100644
--- 
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandlerTest.java
+++ 
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/Http2HttpHandlerTest.java
@@ -25,8 +25,12 @@ import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
 import io.netty.handler.codec.http2.Http2Headers;
 import io.netty.handler.codec.http2.Http2HeadersFrame;
 import io.netty.handler.codec.http2.Http2StreamFrame;
+import org.apache.seata.core.exception.HttpRequestFilterException;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterChain;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterManager;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
@@ -34,7 +38,14 @@ import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
 
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
 
 class Http2HttpHandlerTest {
     private Http2HttpHandler handler;
@@ -84,88 +95,187 @@ class Http2HttpHandlerTest {
 
     @Test
     void testHttp2GetRequestWithParameters() throws Exception {
-        Http2Headers headers = new DefaultHttp2Headers();
-        headers.method("GET");
-        headers.path("/test?param=testValue");
-        Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers, 
true);
-        channel.writeInbound(headersFrame);
-
-        Http2StreamFrame responseHeadersFrame = waitForHttp2Response(5000);
-        assertNotNull(responseHeadersFrame);
-        assertTrue(responseHeadersFrame instanceof DefaultHttp2HeadersFrame);
-        DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame) 
responseHeadersFrame;
-        assertEquals("200", respHeaders.headers().status().toString());
-
-        Http2StreamFrame responseDataFrame = waitForHttp2Response(5000);
-        assertNotNull(responseDataFrame);
-        assertTrue(responseDataFrame instanceof DefaultHttp2DataFrame);
-        DefaultHttp2DataFrame respData = (DefaultHttp2DataFrame) 
responseDataFrame;
-        String content = respData.content().toString(StandardCharsets.UTF_8);
-        assertTrue(content.contains("Processed: testValue"));
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doNothing().when(mockChain).doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+            Http2Headers headers = new DefaultHttp2Headers();
+            headers.method("GET");
+            headers.path("/test?param=testValue");
+            Http2HeadersFrame headersFrame = new 
DefaultHttp2HeadersFrame(headers, true);
+            channel.writeInbound(headersFrame);
+
+            Http2StreamFrame responseHeadersFrame = waitForHttp2Response(5000);
+            assertNotNull(responseHeadersFrame);
+            assertTrue(responseHeadersFrame instanceof 
DefaultHttp2HeadersFrame);
+            DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame) 
responseHeadersFrame;
+            assertEquals("200", respHeaders.headers().status().toString());
+
+            Http2StreamFrame responseDataFrame = waitForHttp2Response(5000);
+            assertNotNull(responseDataFrame);
+            assertTrue(responseDataFrame instanceof DefaultHttp2DataFrame);
+            DefaultHttp2DataFrame respData = (DefaultHttp2DataFrame) 
responseDataFrame;
+            String content = 
respData.content().toString(StandardCharsets.UTF_8);
+            assertTrue(content.contains("Processed: testValue"));
+        }
     }
 
     @Test
     void testHttp2RequestToNonexistentPath() {
-        Http2Headers headers = new DefaultHttp2Headers();
-        headers.method("GET");
-        headers.path("/notfound");
-        Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers, 
true);
-        channel.writeInbound(headersFrame);
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doNothing().when(mockChain).doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+            Http2Headers headers = new DefaultHttp2Headers();
+            headers.method("GET");
+            headers.path("/notfound");
+            Http2HeadersFrame headersFrame = new 
DefaultHttp2HeadersFrame(headers, true);
+            channel.writeInbound(headersFrame);
 
-        Http2StreamFrame responseHeadersFrame = channel.readOutbound();
-        assertTrue(responseHeadersFrame instanceof DefaultHttp2HeadersFrame);
-        DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame) 
responseHeadersFrame;
-        assertEquals("404", respHeaders.headers().status().toString());
+            Http2StreamFrame responseHeadersFrame = channel.readOutbound();
+            assertTrue(responseHeadersFrame instanceof 
DefaultHttp2HeadersFrame);
+            DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame) 
responseHeadersFrame;
+            assertEquals("404", respHeaders.headers().status().toString());
+        }
     }
 
     @Test
     void testHttp2PostRequestWithJsonBody() throws Exception {
-        String json = OBJECT_MAPPER.writeValueAsString(new HashMap<String, 
Object>() {
-            {
-                put("foo", "bar");
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doNothing().when(mockChain).doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+            String json = OBJECT_MAPPER.writeValueAsString(new HashMap<String, 
Object>() {
+                {
+                    put("foo", "bar");
+                }
+            });
+            Http2Headers headers = new DefaultHttp2Headers();
+            headers.method("POST");
+            headers.path("/test?param=jsonValue");
+            Http2HeadersFrame headersFrame = new 
DefaultHttp2HeadersFrame(headers, false);
+            channel.writeInbound(headersFrame);
+            DefaultHttp2DataFrame dataFrame =
+                    new DefaultHttp2DataFrame(Unpooled.copiedBuffer(json, 
StandardCharsets.UTF_8), true);
+            channel.writeInbound(dataFrame);
+
+            Http2StreamFrame frame1 = null, frame2 = null;
+            long deadline = System.currentTimeMillis() + 5000;
+            while ((frame1 == null || frame2 == null) && 
System.currentTimeMillis() < deadline) {
+                if (frame1 == null) frame1 = channel.readOutbound();
+                if (frame2 == null) frame2 = channel.readOutbound();
+                if (frame1 == null || frame2 == null) Thread.sleep(500);
             }
-        });
-        Http2Headers headers = new DefaultHttp2Headers();
-        headers.method("POST");
-        headers.path("/test?param=jsonValue");
-        Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers, 
false);
-        channel.writeInbound(headersFrame);
-        DefaultHttp2DataFrame dataFrame =
-                new DefaultHttp2DataFrame(Unpooled.copiedBuffer(json, 
StandardCharsets.UTF_8), true);
-        channel.writeInbound(dataFrame);
-
-        Http2StreamFrame frame1 = null, frame2 = null;
-        long deadline = System.currentTimeMillis() + 5000; // 最多等5秒
-        while ((frame1 == null || frame2 == null) && 
System.currentTimeMillis() < deadline) {
-            if (frame1 == null) frame1 = channel.readOutbound();
-            if (frame2 == null) frame2 = channel.readOutbound();
-            if (frame1 == null || frame2 == null) Thread.sleep(500);
-        }
-        assertNotNull(frame1);
-        assertNotNull(frame2);
-        DefaultHttp2HeadersFrame respHeaders;
-        DefaultHttp2DataFrame respData;
-        if (frame1 instanceof DefaultHttp2HeadersFrame) {
-            respHeaders = (DefaultHttp2HeadersFrame) frame1;
-            respData = (DefaultHttp2DataFrame) frame2;
-        } else {
-            respHeaders = (DefaultHttp2HeadersFrame) frame2;
-            respData = (DefaultHttp2DataFrame) frame1;
+            assertNotNull(frame1);
+            assertNotNull(frame2);
+            DefaultHttp2HeadersFrame respHeaders;
+            DefaultHttp2DataFrame respData;
+            if (frame1 instanceof DefaultHttp2HeadersFrame) {
+                respHeaders = (DefaultHttp2HeadersFrame) frame1;
+                respData = (DefaultHttp2DataFrame) frame2;
+            } else {
+                respHeaders = (DefaultHttp2HeadersFrame) frame2;
+                respData = (DefaultHttp2DataFrame) frame1;
+            }
+            assertEquals("200", respHeaders.headers().status().toString());
+            String content = 
respData.content().toString(StandardCharsets.UTF_8);
+            assertTrue(content.contains("Processed: jsonValue"));
         }
-        assertEquals("200", respHeaders.headers().status().toString());
-        String content = respData.content().toString(StandardCharsets.UTF_8);
-        assertTrue(content.contains("Processed: jsonValue"));
     }
 
     @Test
     void testHttp2BadRequest() {
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doNothing().when(mockChain).doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+            Http2Headers headers = new DefaultHttp2Headers();
+            Http2HeadersFrame headersFrame = new 
DefaultHttp2HeadersFrame(headers, true);
+            channel.writeInbound(headersFrame);
+            Http2StreamFrame responseHeadersFrame = channel.readOutbound();
+            assertTrue(responseHeadersFrame instanceof 
DefaultHttp2HeadersFrame);
+            DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame) 
responseHeadersFrame;
+            assertEquals("400", respHeaders.headers().status().toString());
+        }
+    }
+
+    @Test
+    void testHttp2GetRequestWithXSSParam_shouldBeBlocked() {
+
         Http2Headers headers = new DefaultHttp2Headers();
+        headers.method("GET");
+        headers.path("/test?param=<script>alert(1)</script>");
         Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers, 
true);
-        channel.writeInbound(headersFrame);
-        Http2StreamFrame responseHeadersFrame = channel.readOutbound();
-        assertTrue(responseHeadersFrame instanceof DefaultHttp2HeadersFrame);
-        DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame) 
responseHeadersFrame;
-        assertEquals("400", respHeaders.headers().status().toString());
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doThrow(new HttpRequestFilterException("Detected javascript:"))
+                    .when(mockChain)
+                    .doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+            channel.writeInbound(headersFrame);
+
+            Http2StreamFrame responseHeadersFrame = waitForHttp2Response(3000);
+            assertNotNull(responseHeadersFrame);
+            assertTrue(responseHeadersFrame instanceof 
DefaultHttp2HeadersFrame);
+            DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame) 
responseHeadersFrame;
+
+            assertEquals("400", respHeaders.headers().status().toString());
+        }
+    }
+
+    @Test
+    void testHttp2GetRequestWithOnloadParam_shouldBeBlocked() {
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doThrow(new HttpRequestFilterException("Detected javascript:"))
+                    .when(mockChain)
+                    .doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+            Http2Headers headers = new DefaultHttp2Headers();
+            headers.method("GET");
+            headers.path("/test?param=onload=alert(1)");
+            Http2HeadersFrame headersFrame = new 
DefaultHttp2HeadersFrame(headers, true);
+
+            channel.writeInbound(headersFrame);
+
+            Http2StreamFrame responseHeadersFrame = waitForHttp2Response(3000);
+            assertNotNull(responseHeadersFrame);
+            assertTrue(responseHeadersFrame instanceof 
DefaultHttp2HeadersFrame);
+            DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame) 
responseHeadersFrame;
+
+            assertEquals("400", respHeaders.headers().status().toString());
+        }
+    }
+
+    @Test
+    void testHttp2PostRequestWithXssJson_shouldBeBlocked() throws Exception {
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doThrow(new HttpRequestFilterException("Detected javascript:"))
+                    .when(mockChain)
+                    .doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+            String maliciousJson = "{\"param\": 
\"<script>alert('xss')</script>\"}";
+
+            Http2Headers headers = new DefaultHttp2Headers();
+            headers.method("POST");
+            headers.path("/test?param=abc");
+            headers.set("content-type", "application/json");
+
+            Http2HeadersFrame headersFrame = new 
DefaultHttp2HeadersFrame(headers, false);
+            DefaultHttp2DataFrame dataFrame =
+                    new 
DefaultHttp2DataFrame(Unpooled.copiedBuffer(maliciousJson, 
StandardCharsets.UTF_8), true);
+
+            channel.writeInbound(headersFrame);
+            channel.writeInbound(dataFrame);
+
+            Http2StreamFrame responseHeadersFrame = waitForHttp2Response(3000);
+            assertNotNull(responseHeadersFrame);
+            assertTrue(responseHeadersFrame instanceof 
DefaultHttp2HeadersFrame);
+            DefaultHttp2HeadersFrame respHeaders = (DefaultHttp2HeadersFrame) 
responseHeadersFrame;
+
+            assertEquals("400", respHeaders.headers().status().toString());
+        }
     }
 
     @org.junit.jupiter.api.AfterEach
diff --git 
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
 
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
index f305543360..efa141e5be 100644
--- 
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
+++ 
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/HttpDispatchHandlerTest.java
@@ -23,8 +23,12 @@ import io.netty.handler.codec.http.HttpMethod;
 import io.netty.handler.codec.http.HttpRequest;
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.netty.handler.codec.http.HttpVersion;
+import org.apache.seata.core.exception.HttpRequestFilterException;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterChain;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterManager;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
@@ -33,6 +37,11 @@ import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
 
 class HttpDispatchHandlerTest {
 
@@ -68,7 +77,10 @@ class HttpDispatchHandlerTest {
 
         ControllerManager.addHttpInvocation(invocation);
 
-        try {
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doNothing().when(mockChain).doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
             HttpRequest request =
                     new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, 
HttpMethod.GET, "/test?param=testValue");
 
@@ -85,23 +97,110 @@ class HttpDispatchHandlerTest {
 
     @Test
     void testRequestToNonexistentPath() {
-        HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, 
HttpMethod.GET, "/notfound");
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doNothing().when(mockChain).doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+            HttpRequest request = new 
DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/notfound");
 
-        channel.writeInbound(request);
+            channel.writeInbound(request);
 
-        FullHttpResponse response = waitForResponse(5000);
-        assertEquals(HttpResponseStatus.NOT_FOUND, response.status());
+            FullHttpResponse response = waitForResponse(5000);
+            assertEquals(HttpResponseStatus.NOT_FOUND, response.status());
+        }
     }
 
     @Test
     void testHttpHeadMethod() {
-        HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, 
HttpMethod.HEAD, "/head");
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doNothing().when(mockChain).doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+            HttpRequest request = new 
DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.HEAD, "/head");
+
+            channel.writeInbound(request);
+
+            FullHttpResponse response = waitForResponse(5000);
+            assertEquals(HttpResponseStatus.NOT_FOUND, response.status());
+            assertEquals(0, response.content().readableBytes());
+        }
+    }
+
+    @Test
+    void testRequestFilteredByHttpRequestFilter() throws Exception {
+        HttpRequestFilterChain mockFilterChain = 
mock(HttpRequestFilterChain.class);
 
-        channel.writeInbound(request);
+        doThrow(new HttpRequestFilterException("Mock filter block"))
+                .when(mockFilterChain)
+                .doFilter(any());
 
-        FullHttpResponse response = waitForResponse(5000);
-        assertEquals(HttpResponseStatus.NOT_FOUND, response.status());
-        assertEquals(0, response.content().readableBytes());
+        MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class);
+        
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockFilterChain);
+
+        try {
+            HttpRequest request = new 
DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/any");
+
+            channel.writeInbound(request);
+
+            FullHttpResponse response = waitForResponse(5000);
+            assertEquals(HttpResponseStatus.BAD_REQUEST, response.status());
+        } finally {
+            mockedStatic.close();
+        }
+    }
+
+    @Test
+    void testRequestFilteredByXssScript() throws Exception {
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doThrow(new HttpRequestFilterException("Detected <script>"))
+                    .when(mockChain)
+                    .doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+
+            HttpRequest request = new DefaultFullHttpRequest(
+                    HttpVersion.HTTP_1_1, HttpMethod.GET, 
"/test?param=<script>alert(1)</script>");
+
+            channel.writeInbound(request);
+            FullHttpResponse response = waitForResponse(5000);
+            assertEquals(HttpResponseStatus.BAD_REQUEST, response.status());
+        }
+    }
+
+    @Test
+    void testRequestFilteredByXssJavascriptUrl() throws Exception {
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doThrow(new HttpRequestFilterException("Detected javascript:"))
+                    .when(mockChain)
+                    .doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+
+            HttpRequest request = new DefaultFullHttpRequest(
+                    HttpVersion.HTTP_1_1, HttpMethod.GET, 
"/test?param=javascript:alert('XSS')");
+
+            channel.writeInbound(request);
+            FullHttpResponse response = waitForResponse(5000);
+            assertEquals(HttpResponseStatus.BAD_REQUEST, response.status());
+        }
+    }
+
+    @Test
+    void testRequestFilteredByXssOnloadEvent() throws Exception {
+        try (MockedStatic<HttpRequestFilterManager> mockedStatic = 
mockStatic(HttpRequestFilterManager.class)) {
+            HttpRequestFilterChain mockChain = 
mock(HttpRequestFilterChain.class);
+            doThrow(new HttpRequestFilterException("Detected onload="))
+                    .when(mockChain)
+                    .doFilter(any());
+            
mockedStatic.when(HttpRequestFilterManager::getFilterChain).thenReturn(mockChain);
+
+            HttpRequest request =
+                    new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, 
HttpMethod.GET, "/test?param=onload=alert(1)");
+
+            channel.writeInbound(request);
+            FullHttpResponse response = waitForResponse(5000);
+            assertEquals(HttpResponseStatus.BAD_REQUEST, response.status());
+        }
     }
 
     private FullHttpResponse waitForResponse(long timeoutMs) {
diff --git 
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterManagerTest.java
 
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterManagerTest.java
new file mode 100644
index 0000000000..d0a5340c33
--- /dev/null
+++ 
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestFilterManagerTest.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.seata.core.rpc.netty.http.filter;
+
+import org.apache.seata.common.ConfigurationKeys;
+import org.apache.seata.common.loader.EnhancedServiceLoader;
+import org.apache.seata.config.Configuration;
+import org.apache.seata.config.ConfigurationFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+class HttpRequestFilterManagerTest {
+
+    interface MockFilter extends HttpRequestFilter {}
+
+    @AfterEach
+    void reset() throws Exception {
+        Field filtersField = 
HttpRequestFilterManager.class.getDeclaredField("HTTP_REQUEST_FILTERS");
+        filtersField.setAccessible(true);
+        List<?> filters = (List<?>) filtersField.get(null);
+        filters.clear();
+
+        Field chainField = 
HttpRequestFilterManager.class.getDeclaredField("HTTP_REQUEST_FILTER_CHAIN");
+        chainField.setAccessible(true);
+        chainField.set(null, null);
+
+        Field initializedField = 
HttpRequestFilterManager.class.getDeclaredField("initialized");
+        initializedField.setAccessible(true);
+        initializedField.setBoolean(null, false);
+    }
+
+    @Test
+    void testInitializeFilters_andGetFilterChain() {
+        MockFilter filter1 = mock(MockFilter.class);
+        MockFilter filter2 = mock(MockFilter.class);
+
+        when(filter1.shouldApply()).thenReturn(true);
+        when(filter1.getOrder()).thenReturn(10);
+        when(filter2.shouldApply()).thenReturn(true);
+        when(filter2.getOrder()).thenReturn(5);
+
+        try (MockedStatic<ConfigurationFactory> configMock = 
mockStatic(ConfigurationFactory.class);
+                MockedStatic<EnhancedServiceLoader> mockedLoader = 
mockStatic(EnhancedServiceLoader.class)) {
+
+            Configuration mockConfig = mock(Configuration.class);
+            
when(mockConfig.getBoolean(ConfigurationKeys.SERVER_HTTP_FILTER_ENABLE, true))
+                    .thenReturn(true);
+            
configMock.when(ConfigurationFactory::getInstance).thenReturn(mockConfig);
+
+            mockedLoader
+                    .when(() -> 
EnhancedServiceLoader.loadAll(HttpRequestFilter.class))
+                    .thenReturn(Arrays.asList(filter1, filter2));
+
+            // init
+            HttpRequestFilterManager.initializeFilters();
+
+            // init again,expect no add
+            HttpRequestFilterManager.initializeFilters();
+
+            HttpRequestFilterChain chain = 
HttpRequestFilterManager.getFilterChain();
+            assertNotNull(chain);
+
+            List<HttpRequestFilter> filters = chain.getFilters();
+            assertEquals(2, filters.size());
+            assertSame(filter2, filters.get(0));
+            assertSame(filter1, filters.get(1));
+        }
+    }
+
+    @Test
+    void testInitializeFilters_filterShouldApplyFalse() {
+        MockFilter filter = mock(MockFilter.class);
+        when(filter.shouldApply()).thenReturn(false);
+
+        try (MockedStatic<ConfigurationFactory> configMock = 
mockStatic(ConfigurationFactory.class);
+                MockedStatic<EnhancedServiceLoader> mockedLoader = 
mockStatic(EnhancedServiceLoader.class)) {
+            Configuration mockConfig = mock(Configuration.class);
+            
when(mockConfig.getBoolean(ConfigurationKeys.SERVER_HTTP_FILTER_ENABLE, true))
+                    .thenReturn(true);
+            
configMock.when(ConfigurationFactory::getInstance).thenReturn(mockConfig);
+            mockedLoader
+                    .when(() -> 
EnhancedServiceLoader.loadAll(HttpRequestFilter.class))
+                    .thenReturn(Arrays.asList(filter));
+
+            HttpRequestFilterManager.initializeFilters();
+
+            HttpRequestFilterChain chain = 
HttpRequestFilterManager.getFilterChain();
+            assertNotNull(chain);
+            assertTrue(chain.getFilters().isEmpty(), "Filters list should be 
empty when shouldApply returns false");
+        }
+    }
+
+    @Test
+    void testGetFilterChain_beforeInitialization_shouldThrow() {
+        IllegalStateException exception =
+                assertThrows(IllegalStateException.class, 
HttpRequestFilterManager::getFilterChain);
+        assertEquals("HttpRequestFilterManager not initialized.", 
exception.getMessage());
+    }
+}
diff --git 
a/core/src/test/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestParamWrapperTest.java
 
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestParamWrapperTest.java
new file mode 100644
index 0000000000..f722def311
--- /dev/null
+++ 
b/core/src/test/java/org/apache/seata/core/rpc/netty/http/filter/HttpRequestParamWrapperTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.seata.core.rpc.netty.http.filter;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.DefaultHttpHeaders;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpVersion;
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class HttpRequestParamWrapperTest {
+
+    @Test
+    void testParseQueryParams() {
+        FullHttpRequest req = mock(FullHttpRequest.class);
+        
when(req.uri()).thenReturn("/path?city=shanghai&city=beijing&hello=world");
+        when(req.headers()).thenReturn(new DefaultHttpHeaders());
+        when(req.content()).thenReturn(Unpooled.EMPTY_BUFFER);
+
+        HttpRequestParamWrapper wrapper = new HttpRequestParamWrapper(req);
+
+        Map<String, List<String>> all = wrapper.getAllParamsAsMultiMap();
+
+        assertThat(all.get("city")).containsExactly("shanghai", "beijing");
+        assertThat(all.get("hello")).containsExactly("world");
+    }
+
+    @Test
+    void testParseHeaders() {
+        FullHttpRequest req = mock(FullHttpRequest.class);
+        when(req.uri()).thenReturn("/path");
+        HttpHeaders headers = new DefaultHttpHeaders();
+        headers.add("X-Custom", "value1");
+        headers.add("X-Custom", "value2");
+        when(req.headers()).thenReturn(headers);
+        when(req.content()).thenReturn(Unpooled.EMPTY_BUFFER);
+
+        HttpRequestParamWrapper wrapper = new HttpRequestParamWrapper(req);
+
+        Map<String, List<String>> all = wrapper.getAllParamsAsMultiMap();
+
+        assertThat(all.get("X-Custom")).containsExactly("value1", "value2");
+    }
+
+    @Test
+    void testParseJsonBody() {
+        FullHttpRequest req = mock(FullHttpRequest.class);
+        when(req.uri()).thenReturn("/path");
+        HttpHeaders headers = new DefaultHttpHeaders();
+        headers.set(HttpHeaderNames.CONTENT_TYPE, "application/json");
+        when(req.headers()).thenReturn(headers);
+
+        String json = "{\"key1\":\"value1\", \"key2\":\"value2\"}";
+        ByteBuf buf = Unpooled.copiedBuffer(json, StandardCharsets.UTF_8);
+        when(req.content()).thenReturn(buf);
+
+        HttpRequestParamWrapper wrapper = new HttpRequestParamWrapper(req);
+
+        Map<String, List<String>> all = wrapper.getAllParamsAsMultiMap();
+
+        assertThat(all.get("key1")).containsExactly("value1");
+        assertThat(all.get("key2")).containsExactly("value2");
+    }
+
+    @Test
+    void testParseFormBody() throws Exception {
+        FullHttpRequest req = mock(FullHttpRequest.class);
+        when(req.method()).thenReturn(HttpMethod.POST);
+        when(req.uri()).thenReturn("/path");
+        HttpHeaders headers = new DefaultHttpHeaders();
+        headers.set(HttpHeaderNames.CONTENT_TYPE, 
"application/x-www-form-urlencoded");
+        when(req.headers()).thenReturn(headers);
+
+        String formBody = "param1=value1&param2=value2";
+        ByteBuf buf = Unpooled.copiedBuffer(formBody, StandardCharsets.UTF_8);
+        when(req.content()).thenReturn(buf);
+
+        DefaultFullHttpRequest realReq =
+                new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, 
HttpMethod.POST, "/path", buf.retainedDuplicate());
+        realReq.headers().set(HttpHeaderNames.CONTENT_TYPE, 
"application/x-www-form-urlencoded");
+
+        HttpRequestParamWrapper wrapper = new HttpRequestParamWrapper(realReq);
+
+        Map<String, List<String>> all = wrapper.getAllParamsAsMultiMap();
+
+        assertThat(all.get("param1")).containsExactly("value1");
+        assertThat(all.get("param2")).containsExactly("value2");
+    }
+}
diff --git a/script/config-center/config.txt b/script/config-center/config.txt
index dc140e6fdb..c4cad2070c 100644
--- a/script/config-center/config.txt
+++ b/script/config-center/config.txt
@@ -205,6 +205,10 @@ server.ratelimit.bucketTokenNumPerSecond = 999999
 server.ratelimit.bucketTokenMaxNum = 999999
 server.ratelimit.bucketTokenInitialNum = 999999
 
+server.http.filter.enabled=true
+server.http.filter.xss.enabled=true
+server.http.filter.xss.keywords=["<script>", "</script>", "javascript:", 
"vbscript:"]
+
 #Metrics configuration, only for the server
 metrics.enabled=true
 metrics.registryType=compact
diff --git 
a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java
 
b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java
index e6fc8c5b4b..cb04e37b16 100644
--- 
a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java
+++ 
b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java
@@ -77,6 +77,10 @@ public interface StarterConstants {
     String SERVER_RAFT_SSL_SERVER_KEYSTORE_PREFIX = SERVER_RAFT_SSL_PREFIX + 
".server.keystore";
     String SERVER_RECOVERY_PREFIX = SERVER_PREFIX + ".recovery";
 
+    String SERVER_HTTP_PREFIX = SERVER_PREFIX + ".http";
+
+    String SERVER_HTTP_FILTER_PREFIX = SERVER_HTTP_PREFIX + ".filter";
+
     String METRICS_PREFIX = SEATA_PREFIX + ".metrics";
 
     String STORE_PREFIX = SEATA_PREFIX + ".store";
diff --git 
a/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataServerEnvironmentPostProcessor.java
 
b/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataServerEnvironmentPostProcessor.java
index 1a9c1b813b..caa761966b 100644
--- 
a/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataServerEnvironmentPostProcessor.java
+++ 
b/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataServerEnvironmentPostProcessor.java
@@ -21,6 +21,7 @@ import 
org.apache.seata.spring.boot.autoconfigure.properties.server.ServerProper
 import 
org.apache.seata.spring.boot.autoconfigure.properties.server.ServerRateLimitProperties;
 import 
org.apache.seata.spring.boot.autoconfigure.properties.server.ServerRecoveryProperties;
 import 
org.apache.seata.spring.boot.autoconfigure.properties.server.ServerUndoProperties;
+import 
org.apache.seata.spring.boot.autoconfigure.properties.server.filter.ServerHttpFilterProperties;
 import 
org.apache.seata.spring.boot.autoconfigure.properties.server.raft.ServerRaftProperties;
 import 
org.apache.seata.spring.boot.autoconfigure.properties.server.raft.ServerRaftSSLClientProperties;
 import 
org.apache.seata.spring.boot.autoconfigure.properties.server.raft.ServerRaftSSLProperties;
@@ -44,6 +45,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import static 
org.apache.seata.spring.boot.autoconfigure.StarterConstants.METRICS_PREFIX;
 import static 
org.apache.seata.spring.boot.autoconfigure.StarterConstants.PROPERTY_BEAN_MAP;
+import static 
org.apache.seata.spring.boot.autoconfigure.StarterConstants.SERVER_HTTP_FILTER_PREFIX;
 import static 
org.apache.seata.spring.boot.autoconfigure.StarterConstants.SERVER_PREFIX;
 import static 
org.apache.seata.spring.boot.autoconfigure.StarterConstants.SERVER_RAFT_PREFIX;
 import static 
org.apache.seata.spring.boot.autoconfigure.StarterConstants.SERVER_RAFT_SSL_CLIENT_KEYSTORE_PREFIX;
@@ -102,6 +104,7 @@ public class SeataServerEnvironmentPostProcessor implements 
EnvironmentPostProce
             PROPERTY_BEAN_MAP.put(SESSION_PREFIX, SessionProperties.class);
             PROPERTY_BEAN_MAP.put(STORE_PREFIX, StoreProperties.class);
             PROPERTY_BEAN_MAP.put(SERVER_RATELIMIT_PREFIX, 
ServerRateLimitProperties.class);
+            PROPERTY_BEAN_MAP.put(SERVER_HTTP_FILTER_PREFIX, 
ServerHttpFilterProperties.class);
         }
     }
 }
diff --git 
a/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/server/filter/ServerHttpFilterProperties.java
 
b/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/server/filter/ServerHttpFilterProperties.java
new file mode 100644
index 0000000000..ea7a08fe52
--- /dev/null
+++ 
b/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/server/filter/ServerHttpFilterProperties.java
@@ -0,0 +1,72 @@
+/*
+ * 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.seata.spring.boot.autoconfigure.properties.server.filter;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+import static org.apache.seata.common.DefaultValues.DEFAULT_XSS_KEYWORDS;
+import static 
org.apache.seata.spring.boot.autoconfigure.StarterConstants.SERVER_HTTP_FILTER_PREFIX;
+
+@Component
+@ConfigurationProperties(prefix = SERVER_HTTP_FILTER_PREFIX)
+public class ServerHttpFilterProperties {
+    private boolean enabled = true;
+
+    private Xss xss = new Xss();
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public Xss getXss() {
+        return xss;
+    }
+
+    public void setXss(Xss xss) {
+        this.xss = xss;
+    }
+
+    public static class Xss {
+
+        private boolean enabled = true;
+
+        private List<String> keywords = DEFAULT_XSS_KEYWORDS;
+
+        public boolean isEnabled() {
+            return enabled;
+        }
+
+        public void setEnabled(boolean enabled) {
+            this.enabled = enabled;
+        }
+
+        public List<String> getKeywords() {
+            return keywords;
+        }
+
+        public void setKeywords(List<String> keywords) {
+            this.keywords = keywords;
+        }
+    }
+}
diff --git 
a/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/server/filter/ServerHttpFilterPropertiesTest.java
 
b/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/server/filter/ServerHttpFilterPropertiesTest.java
new file mode 100644
index 0000000000..b7c77df49b
--- /dev/null
+++ 
b/seata-spring-autoconfigure/seata-spring-autoconfigure-server/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/server/filter/ServerHttpFilterPropertiesTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.seata.spring.boot.autoconfigure.properties.server.filter;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+
+public class ServerHttpFilterPropertiesTest {
+
+    @Test
+    public void testServerHttpFilterProperties() {
+        ServerHttpFilterProperties serverHttpFilterProperties = new 
ServerHttpFilterProperties();
+
+        ServerHttpFilterProperties.Xss xss = new 
ServerHttpFilterProperties.Xss();
+
+        serverHttpFilterProperties.setXss(xss);
+
+        Assertions.assertTrue(serverHttpFilterProperties.isEnabled());
+        Assertions.assertTrue(serverHttpFilterProperties.getXss().isEnabled());
+        Assertions.assertEquals(
+                "<script>", 
serverHttpFilterProperties.getXss().getKeywords().get(0));
+    }
+
+    @Test
+    public void testServerHttpFilterPropertiesUnDefaultValue() {
+        ServerHttpFilterProperties serverHttpFilterProperties = new 
ServerHttpFilterProperties();
+        serverHttpFilterProperties.setEnabled(false);
+        ServerHttpFilterProperties.Xss xss = new 
ServerHttpFilterProperties.Xss();
+        xss.setKeywords(Collections.singletonList("<alert>"));
+        serverHttpFilterProperties.setXss(xss);
+
+        Assertions.assertFalse(serverHttpFilterProperties.isEnabled());
+        Assertions.assertTrue(serverHttpFilterProperties.getXss().isEnabled());
+        Assertions.assertEquals(
+                "<alert>", 
serverHttpFilterProperties.getXss().getKeywords().get(0));
+    }
+}
diff --git 
a/server/src/main/java/org/apache/seata/server/filter/XSSHttpRequestFilter.java 
b/server/src/main/java/org/apache/seata/server/filter/XSSHttpRequestFilter.java
new file mode 100644
index 0000000000..3c6c9ded0e
--- /dev/null
+++ 
b/server/src/main/java/org/apache/seata/server/filter/XSSHttpRequestFilter.java
@@ -0,0 +1,141 @@
+/*
+ * 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.seata.server.filter;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.seata.common.loader.LoadLevel;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.config.Configuration;
+import org.apache.seata.config.ConfigurationFactory;
+import org.apache.seata.config.ConfigurationKeys;
+import org.apache.seata.core.exception.HttpRequestFilterException;
+import org.apache.seata.core.rpc.netty.http.filter.HttpFilterContext;
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilter;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static 
org.apache.seata.common.ConfigurationKeys.SERVER_HTTP_FILTER_XSS_FILTER_KEYWORDS;
+import static org.apache.seata.common.DefaultValues.DEFAULT_XSS_KEYWORDS;
+
+/**
+ * Filter to detect and block potential XSS attack vectors in HTTP request 
parameters.
+ */
+@LoadLevel(name = "XSS", order = 1)
+public class XSSHttpRequestFilter implements HttpRequestFilter {
+    /**
+     * The constant CONFIG.
+     */
+    private static final Configuration CONFIG = 
ConfigurationFactory.getInstance();
+
+    private final List<String> xssKeywords;
+
+    private static final int MAX_EVENT_HANDLER_LENGTH = 50;
+
+    private static final int ON_REPEAT_LIMIT = 5;
+
+    private static final Pattern ON_REPEAT_PATTERN =
+            Pattern.compile("(on){" + ON_REPEAT_LIMIT + ",}", 
Pattern.CASE_INSENSITIVE);
+
+    private static final Pattern EVENT_HANDLER_PATTERN = Pattern.compile(
+            "\\bon([a-zA-Z0-9]{1," + MAX_EVENT_HANDLER_LENGTH + 
"}?)\\s*=\\s*['\"][^'\"]*['\"]",
+            Pattern.CASE_INSENSITIVE);
+
+    public XSSHttpRequestFilter() {
+        String xssKeywordConfig = 
CONFIG.getConfig(SERVER_HTTP_FILTER_XSS_FILTER_KEYWORDS, null);
+
+        if (StringUtils.isBlank(xssKeywordConfig)) {
+            this.xssKeywords = DEFAULT_XSS_KEYWORDS;
+        } else {
+            ObjectMapper objectMapper = new ObjectMapper();
+            try {
+                xssKeywords = objectMapper.readValue(xssKeywordConfig, new 
TypeReference<List<String>>() {});
+            } catch (JsonProcessingException e) {
+                throw new IllegalArgumentException(
+                        "Invalid format for configuration 
'server.http.filter.xss.keywords'. "
+                                + "Expected a JSON array like [\"<script>\", 
\"vbscript:\"], but got: "
+                                + xssKeywordConfig,
+                        e);
+            }
+        }
+    }
+
+    @Override
+    public int getOrder() {
+        return 1;
+    }
+
+    /**
+     * Checks all request parameters for XSS risks and throws if found.
+     */
+    @Override
+    public void doFilter(HttpFilterContext<?> context) throws 
HttpRequestFilterException {
+        Map<String, List<String>> allParams = 
context.getParamWrapper().getAllParamsAsMultiMap();
+        for (Map.Entry<String, List<String>> entry : allParams.entrySet()) {
+            for (String value : entry.getValue()) {
+                if (containsXssRisk(value)) {
+                    throw new HttpRequestFilterException(
+                            "XSS risk detected in param: " + entry.getKey() + 
", value: " + value);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns whether this XSS filter is enabled via configuration.
+     */
+    @Override
+    public boolean shouldApply() {
+        return ConfigurationFactory.getInstance()
+                
.getBoolean(ConfigurationKeys.SERVER_HTTP_FILTER_XSS_FILTER_ENABLE, true);
+    }
+
+    /**
+     * Basic check for common XSS patterns in a string value.
+     */
+    private boolean containsXssRisk(String value) {
+        if (value == null) {
+            return false;
+        }
+
+        String normalized = value.toLowerCase().replaceAll("\\s+", "");
+
+        for (String keyword : xssKeywords) {
+            if (normalized.contains(keyword)) {
+                return true;
+            }
+        }
+
+        if (ON_REPEAT_PATTERN.matcher(value).find()) {
+            return true;
+        }
+
+        Matcher matcher = EVENT_HANDLER_PATTERN.matcher(value);
+        while (matcher.find()) {
+            String eventName = matcher.group(1);
+            if (eventName.length() > MAX_EVENT_HANDLER_LENGTH) {
+                return true;
+            }
+            return true;
+        }
+        return false;
+    }
+}
diff --git 
a/server/src/main/java/org/apache/seata/server/spring/listener/HttpFilterInitListener.java
 
b/server/src/main/java/org/apache/seata/server/spring/listener/HttpFilterInitListener.java
new file mode 100644
index 0000000000..cfc0fff88c
--- /dev/null
+++ 
b/server/src/main/java/org/apache/seata/server/spring/listener/HttpFilterInitListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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.seata.server.spring.listener;
+
+import org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilterManager;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+
+public class HttpFilterInitListener implements 
ApplicationListener<ContextRefreshedEvent> {
+
+    private static volatile boolean initialized = false;
+
+    @Override
+    public void onApplicationEvent(ContextRefreshedEvent event) {
+        if (!initialized) {
+            HttpRequestFilterManager.initializeFilters();
+            initialized = true;
+        }
+    }
+}
diff --git a/server/src/main/resources/META-INF/spring.factories 
b/server/src/main/resources/META-INF/services/org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilter
similarity index 87%
copy from server/src/main/resources/META-INF/spring.factories
copy to 
server/src/main/resources/META-INF/services/org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilter
index 649fa8a356..09ff4f7e00 100644
--- a/server/src/main/resources/META-INF/spring.factories
+++ 
b/server/src/main/resources/META-INF/services/org.apache.seata.core.rpc.netty.http.filter.HttpRequestFilter
@@ -14,5 +14,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-org.springframework.context.ApplicationListener=\
-org.apache.seata.server.spring.listener.ServerApplicationListener
\ No newline at end of file
+org.apache.seata.server.filter.XSSHttpRequestFilter
\ No newline at end of file
diff --git a/server/src/main/resources/META-INF/spring.factories 
b/server/src/main/resources/META-INF/spring.factories
index 649fa8a356..c0dd6f405e 100644
--- a/server/src/main/resources/META-INF/spring.factories
+++ b/server/src/main/resources/META-INF/spring.factories
@@ -15,4 +15,5 @@
 # limitations under the License.
 #
 org.springframework.context.ApplicationListener=\
-org.apache.seata.server.spring.listener.ServerApplicationListener
\ No newline at end of file
+org.apache.seata.server.spring.listener.ServerApplicationListener,\
+org.apache.seata.server.spring.listener.HttpFilterInitListener
diff --git a/server/src/main/resources/application.example.yml 
b/server/src/main/resources/application.example.yml
index b3aafbe4d9..0bfb0c6bfe 100644
--- a/server/src/main/resources/application.example.yml
+++ b/server/src/main/resources/application.example.yml
@@ -174,6 +174,12 @@ seata:
       bucketTokenNumPerSecond: 999999
       bucketTokenMaxNum: 999999
       bucketTokenInitialNum: 999999
+    http:
+      filter:
+        enabled: true
+        xss:
+          enabled: true
+          keywords: ["<script>", "</script>", "javascript:", "vbscript:"]
   store:
     # support: file 、 db 、 redis 、 raft
     mode: file
diff --git a/server/src/main/resources/application.raft.example.yml 
b/server/src/main/resources/application.raft.example.yml
index 14ce8432fa..fa78e36af3 100644
--- a/server/src/main/resources/application.raft.example.yml
+++ b/server/src/main/resources/application.raft.example.yml
@@ -150,6 +150,12 @@ seata:
       bucketTokenNumPerSecond: 999999
       bucketTokenMaxNum: 999999
       bucketTokenInitialNum: 999999
+    http:
+      filter:
+        enabled: true
+        xss:
+          enabled: true
+          keywords: ["<script>", "</script>", "javascript:", "vbscript:"]
   store:
     # support: file
     mode: raft
diff --git 
a/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java
 
b/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java
index 12670d7b9c..f78aad1bc2 100644
--- 
a/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java
+++ 
b/server/src/test/java/org/apache/seata/server/controller/ClusterControllerTest.java
@@ -36,6 +36,8 @@ import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.annotation.Import;
 
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -101,4 +103,94 @@ class ClusterControllerTest {
         }
         Assertions.fail();
     }
+
+    @Test
+    @Order(3)
+    void testXssFilterBlocked_queryParam() throws Exception {
+        int port = 
Integer.parseInt(System.getProperty(SERVER_SERVICE_PORT_CAMEL, "8091"));
+        String malicious = "<script>alert('xss')</script>";
+        Map<String, String> header = new HashMap<>();
+        header.put(HTTP.CONTENT_TYPE, 
ContentType.APPLICATION_FORM_URLENCODED.getMimeType());
+        try (CloseableHttpResponse response = HttpClientUtil.doGet(
+                "http://127.0.0.1:"; + port + 
"/metadata/v1/watch?timeout=3000&testParam="
+                        + URLEncoder.encode(malicious, 
String.valueOf(StandardCharsets.UTF_8)),
+                new HashMap<>(),
+                header,
+                5000)) {
+            Assertions.assertEquals(
+                    HttpStatus.SC_BAD_REQUEST, 
response.getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    @Order(4)
+    void testXssFilterBlocked_formParam() throws Exception {
+        Map<String, String> headers = new HashMap<>();
+        headers.put(HTTP.CONTENT_TYPE, 
ContentType.APPLICATION_FORM_URLENCODED.getMimeType());
+
+        Map<String, String> params = new HashMap<>();
+        params.put("testParam", "<script>alert('xss')</script>");
+
+        int port = 
Integer.parseInt(System.getProperty(SERVER_SERVICE_PORT_CAMEL, "8091"));
+        try (CloseableHttpResponse response = HttpClientUtil.doPost(
+                "http://127.0.0.1:"; + port + 
"/metadata/v1/watch?timeout=3000", params, headers, 5000)) {
+            Assertions.assertEquals(
+                    HttpStatus.SC_BAD_REQUEST, 
response.getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    @Order(5)
+    void testXssFilterBlocked_jsonBody() throws Exception {
+        Map<String, String> headers = new HashMap<>();
+        headers.put(HTTP.CONTENT_TYPE, 
ContentType.APPLICATION_JSON.getMimeType());
+
+        String jsonBody = "{\"testParam\":\"<script>alert('xss')</script>\"}";
+
+        int port = 
Integer.parseInt(System.getProperty(SERVER_SERVICE_PORT_CAMEL, "8091"));
+        try (CloseableHttpResponse response = HttpClientUtil.doPostJson(
+                "http://127.0.0.1:"; + port + 
"/metadata/v1/watch?timeout=3000", jsonBody, headers, 5000)) {
+            Assertions.assertEquals(
+                    HttpStatus.SC_BAD_REQUEST, 
response.getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    @Order(6)
+    void testXssFilterBlocked_headerParam() throws Exception {
+        Map<String, String> headers = new HashMap<>();
+        headers.put(HTTP.CONTENT_TYPE, 
ContentType.APPLICATION_FORM_URLENCODED.getMimeType());
+        headers.put("X-Test-Header", "<script>alert('xss')</script>");
+
+        Map<String, String> params = new HashMap<>();
+        params.put("safeParam", "123");
+
+        int port = 
Integer.parseInt(System.getProperty(SERVER_SERVICE_PORT_CAMEL, "8091"));
+        try (CloseableHttpResponse response = HttpClientUtil.doPost(
+                "http://127.0.0.1:"; + port + 
"/metadata/v1/watch?timeout=3000", params, headers, 5000)) {
+            Assertions.assertEquals(
+                    HttpStatus.SC_BAD_REQUEST, 
response.getStatusLine().getStatusCode());
+        }
+    }
+
+    @Test
+    @Order(7)
+    void testXssFilterBlocked_multiSource() throws Exception {
+        Map<String, String> headers = new HashMap<>();
+        headers.put(HTTP.CONTENT_TYPE, 
ContentType.APPLICATION_JSON.getMimeType());
+        headers.put("X-Test-Header", "<script>alert('xss')</script>");
+
+        String jsonBody = "{\"testParam\":\"<script>alert('xss')</script>\"}";
+
+        int port = 
Integer.parseInt(System.getProperty(SERVER_SERVICE_PORT_CAMEL, "8091"));
+        try (CloseableHttpResponse response = HttpClientUtil.doPostJson(
+                "http://127.0.0.1:"; + port + 
"/metadata/v1/watch?timeout=3000&urlParam="
+                        + URLEncoder.encode("<script>alert('xss')</script>", 
String.valueOf(StandardCharsets.UTF_8)),
+                jsonBody,
+                headers,
+                5000)) {
+            Assertions.assertEquals(
+                    HttpStatus.SC_BAD_REQUEST, 
response.getStatusLine().getStatusCode());
+        }
+    }
 }
diff --git a/server/src/test/resources/application.properties 
b/server/src/test/resources/application.properties
index 212c6fbb8f..cbc2472198 100644
--- a/server/src/test/resources/application.properties
+++ b/server/src/test/resources/application.properties
@@ -24,4 +24,6 @@ seata.metrics.registry-type=compact
 seata.server.service-port=8091
 seata.registry.namingserver.server-addr=127.0.0.1:8081
 seata.registry.namingserver.namespace=public
-seata.registry.namingserver.heartbeat-period=5000
\ No newline at end of file
+seata.registry.namingserver.heartbeat-period=5000
+seata.server.http.filter.xss.enabled=true
+seata.server.http.filter.xss.keywords=["<script>", "</script>", "javascript:", 
"vbscript:"]
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to