diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionService.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionService.java
index 276f4059d..fdd173ed7 100644
--- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionService.java
+++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionService.java
@@ -4,6 +4,8 @@ public interface EncryptionService {
 
     String encryptString(String plaintext);
 
+    String encryptStringForNodeServer(String plaintext);
+
     String decryptString(String encryptedText);
 
     String encryptPassword(String plaintext);
diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java
index 6524682b6..72eeba412 100644
--- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java
+++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/encryption/EncryptionServiceImpl.java
@@ -5,6 +5,7 @@
 import org.lowcoder.sdk.config.CommonConfig;
 import org.lowcoder.sdk.config.CommonConfig.Encrypt;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.encrypt.Encryptors;
 import org.springframework.security.crypto.encrypt.TextEncryptor;
@@ -14,13 +15,18 @@
 public class EncryptionServiceImpl implements EncryptionService {
 
     private final TextEncryptor textEncryptor;
+    private final TextEncryptor textEncryptorForNodeServer;
     private final BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
 
     @Autowired
-    public EncryptionServiceImpl(CommonConfig commonConfig) {
+    public EncryptionServiceImpl(
+        CommonConfig commonConfig
+    ) {
         Encrypt encrypt = commonConfig.getEncrypt();
         String saltInHex = Hex.encodeHexString(encrypt.getSalt().getBytes());
         this.textEncryptor = Encryptors.text(encrypt.getPassword(), saltInHex);
+        String saltInHexForNodeServer = Hex.encodeHexString(commonConfig.getJsExecutor().getSalt().getBytes());
+        this.textEncryptorForNodeServer = Encryptors.text(commonConfig.getJsExecutor().getPassword(), saltInHexForNodeServer);
     }
 
     @Override
@@ -30,6 +36,13 @@ public String encryptString(String plaintext) {
         }
         return textEncryptor.encrypt(plaintext);
     }
+    @Override
+    public String encryptStringForNodeServer(String plaintext) {
+        if (StringUtils.isEmpty(plaintext)) {
+            return plaintext;
+        }
+        return textEncryptorForNodeServer.encrypt(plaintext);
+    }
 
     @Override
     public String decryptString(String encryptedText) {
diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java
index f2aa878bb..5f0da78c4 100644
--- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java
+++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/plugin/client/DatasourcePluginClient.java
@@ -5,10 +5,12 @@
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.MapUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.lowcoder.domain.encryption.EncryptionService;
 import org.lowcoder.domain.plugin.client.dto.DatasourcePluginDefinition;
 import org.lowcoder.domain.plugin.client.dto.GetPluginDynamicConfigRequestDTO;
 import org.lowcoder.infra.js.NodeServerClient;
 import org.lowcoder.infra.js.NodeServerHelper;
+import org.lowcoder.sdk.config.CommonConfig;
 import org.lowcoder.sdk.config.CommonConfigHelper;
 import org.lowcoder.sdk.exception.ServerException;
 import org.lowcoder.sdk.models.DatasourceTestResult;
@@ -30,6 +32,8 @@
 
 import static org.lowcoder.sdk.constants.GlobalContext.REQUEST;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+
 @Slf4j
 @RequiredArgsConstructor
 @Component
@@ -45,13 +49,17 @@ public class DatasourcePluginClient implements NodeServerClient {
             .build();
 
     private final CommonConfigHelper commonConfigHelper;
+    private final CommonConfig commonConfig;
     private final NodeServerHelper nodeServerHelper;
+    private final EncryptionService encryptionService;
 
     private static final String PLUGINS_PATH = "plugins";
     private static final String RUN_PLUGIN_QUERY = "runPluginQuery";
     private static final String VALIDATE_PLUGIN_DATA_SOURCE_CONFIG = "validatePluginDataSourceConfig";
     private static final String GET_PLUGIN_DYNAMIC_CONFIG = "getPluginDynamicConfig";
 
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
     public Mono<List<Object>> getPluginDynamicConfigSafely(List<GetPluginDynamicConfigRequestDTO> getPluginDynamicConfigRequestDTOS) {
         return getPluginDynamicConfig(getPluginDynamicConfigRequestDTOS)
                 .onErrorResume(throwable -> {
@@ -119,21 +127,47 @@ public Flux<DatasourcePluginDefinition> getDatasourcePluginDefinitions() {
     @SuppressWarnings("unchecked")
     public Mono<QueryExecutionResult> executeQuery(String pluginName, Object queryDsl, List<Map<String, Object>> context, Object datasourceConfig) {
         return getAcceptLanguage()
-                .flatMap(language -> WEB_CLIENT
-                        .post()
-                        .uri(nodeServerHelper.createUri(RUN_PLUGIN_QUERY))
-                        .header(HttpHeaders.ACCEPT_LANGUAGE, language)
-                        .bodyValue(Map.of("pluginName", pluginName, "dsl", queryDsl, "context", context, "dataSourceConfig", datasourceConfig))
-                        .exchangeToMono(response -> {
-                            if (response.statusCode().is2xxSuccessful()) {
-                                return response.bodyToMono(Map.class)
-                                        .map(map -> map.get("result"))
-                                        .map(QueryExecutionResult::success);
-                            }
-                            return response.bodyToMono(Map.class)
-                                    .map(map -> MapUtils.getString(map, "message"))
-                                    .map(QueryExecutionResult::errorWithMessage);
-                        }));
+                .flatMap(language -> {
+                    try {
+                        Map<String, Object> body = Map.of(
+                                "pluginName", pluginName,
+                                "dsl", queryDsl,
+                                "context", context,
+                                "dataSourceConfig", datasourceConfig
+                        );
+                        String json = OBJECT_MAPPER.writeValueAsString(body);
+
+                        boolean encryptionEnabled = commonConfig.getJsExecutor().isEncrypted();
+                        String payload;
+                        WebClient.RequestBodySpec requestSpec = WEB_CLIENT
+                                .post()
+                                .uri(nodeServerHelper.createUri(RUN_PLUGIN_QUERY))
+                                .header(HttpHeaders.ACCEPT_LANGUAGE, language);
+
+                        if (encryptionEnabled) {
+                            payload = encryptionService.encryptStringForNodeServer(json);
+                            requestSpec = requestSpec.header("X-Encrypted", "true");
+                        } else {
+                            payload = json;
+                        }
+
+                        return requestSpec
+                                .bodyValue(payload)
+                                .exchangeToMono(response -> {
+                                    if (response.statusCode().is2xxSuccessful()) {
+                                        return response.bodyToMono(Map.class)
+                                                .map(map -> map.get("result"))
+                                                .map(QueryExecutionResult::success);
+                                    }
+                                    return response.bodyToMono(Map.class)
+                                            .map(map -> MapUtils.getString(map, "message"))
+                                            .map(QueryExecutionResult::errorWithMessage);
+                                });
+                    } catch (Exception e) {
+                        log.error("Encryption error", e);
+                        return Mono.error(new ServerException("Encryption error"));
+                    }
+                });
     }
 
     @SuppressWarnings("unchecked")
diff --git a/server/api-service/lowcoder-domain/src/test/java/org/lowcoder/domain/encryption/EncryptionServiceImplTest.java b/server/api-service/lowcoder-domain/src/test/java/org/lowcoder/domain/encryption/EncryptionServiceImplTest.java
new file mode 100644
index 000000000..41bd465c8
--- /dev/null
+++ b/server/api-service/lowcoder-domain/src/test/java/org/lowcoder/domain/encryption/EncryptionServiceImplTest.java
@@ -0,0 +1,80 @@
+package org.lowcoder.domain.encryption;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.lowcoder.sdk.config.CommonConfig;
+import org.lowcoder.sdk.config.CommonConfig.Encrypt;
+import org.lowcoder.sdk.config.CommonConfig.JsExecutor;
+import org.springframework.security.crypto.encrypt.Encryptors;
+import org.springframework.security.crypto.encrypt.TextEncryptor;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class EncryptionServiceImplTest {
+
+    private EncryptionServiceImpl encryptionService;
+    private TextEncryptor nodeServerEncryptor;
+    private String nodePassword = "nodePassword";
+    private String nodeSalt = "nodeSalt";
+
+    @BeforeEach
+    void setUp() {
+        // Mock CommonConfig and its nested classes
+        Encrypt encrypt = mock(Encrypt.class);
+        when(encrypt.getPassword()).thenReturn("testPassword");
+        when(encrypt.getSalt()).thenReturn("testSalt");
+
+        JsExecutor jsExecutor = mock(JsExecutor.class);
+        when(jsExecutor.getPassword()).thenReturn(nodePassword);
+        when(jsExecutor.getSalt()).thenReturn(nodeSalt);
+
+        CommonConfig commonConfig = mock(CommonConfig.class);
+        when(commonConfig.getEncrypt()).thenReturn(encrypt);
+        when(commonConfig.getJsExecutor()).thenReturn(jsExecutor);
+
+        encryptionService = new EncryptionServiceImpl(commonConfig);
+
+        // For direct comparison in test
+        String saltInHexForNodeServer = org.apache.commons.codec.binary.Hex.encodeHexString(nodeSalt.getBytes());
+        nodeServerEncryptor = Encryptors.text(nodePassword, saltInHexForNodeServer);
+    }
+
+    @Test
+    void testEncryptStringForNodeServer_NullInput() {
+        assertNull(encryptionService.encryptStringForNodeServer(null));
+    }
+
+    @Test
+    void testEncryptStringForNodeServer_EmptyInput() {
+        assertEquals("", encryptionService.encryptStringForNodeServer(""));
+    }
+
+    @Test
+    void testEncryptStringForNodeServer_EncryptsAndDecryptsCorrectly() {
+        String plain = "node secret";
+        String encrypted = encryptionService.encryptStringForNodeServer(plain);
+        assertNotNull(encrypted);
+        assertNotEquals(plain, encrypted);
+
+        // Decrypt using the same encryptor to verify correctness
+        String decrypted = nodeServerEncryptor.decrypt(encrypted);
+        assertEquals(plain, decrypted);
+    }
+
+    @Test
+    void testEncryptStringForNodeServer_DifferentInputsProduceDifferentOutputs() {
+        String encrypted1 = encryptionService.encryptStringForNodeServer("abc");
+        String encrypted2 = encryptionService.encryptStringForNodeServer("def");
+        assertNotEquals(encrypted1, encrypted2);
+    }
+
+    @Test
+    void testEncryptStringForNodeServer_SameInputProducesDifferentOutputs() {
+        String input = "repeat";
+        String encrypted1 = encryptionService.encryptStringForNodeServer(input);
+        String encrypted2 = encryptionService.encryptStringForNodeServer(input);
+        // Spring's Encryptors.text uses random IV, so outputs should differ
+        assertNotEquals(encrypted1, encrypted2);
+    }
+}
\ No newline at end of file
diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java
index b50d06935..697f42fcd 100644
--- a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java
+++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java
@@ -147,6 +147,9 @@ public long getMaxAgeInSeconds() {
     @Data
     public static class JsExecutor {
         private String host;
+        private String password;
+        private String salt;
+        private boolean isEncrypted;
     }
 
     @Data
diff --git a/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml b/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml
index d52888ca7..050e8077b 100644
--- a/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml
+++ b/server/api-service/lowcoder-server/src/main/resources/application-debug.yaml
@@ -37,6 +37,9 @@ common:
   cookie-name: LOWCODER_DEBUG_TOKEN
   js-executor:
     host: "http://127.0.0.1:6060"
+    password: ${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd}
+    salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt}
+    is-encrypted: ${LOWCODER_NODE_SERVICE_ENCRYPTED:false}
   workspace:
     mode: ${LOWCODER_WORKSPACE_MODE:SAAS}
   plugin-dirs:
diff --git a/server/api-service/lowcoder-server/src/main/resources/application.yaml b/server/api-service/lowcoder-server/src/main/resources/application.yaml
index e5058563c..00bce2fc1 100644
--- a/server/api-service/lowcoder-server/src/main/resources/application.yaml
+++ b/server/api-service/lowcoder-server/src/main/resources/application.yaml
@@ -74,6 +74,9 @@ common:
     corsAllowedDomainString: ${LOWCODER_CORS_DOMAINS:*}
   js-executor:
     host: ${LOWCODER_NODE_SERVICE_URL:http://127.0.0.1:6060}
+    password: ${LOWCODER_NODE_SERVICE_SECRET:lowcoderpwd}
+    salt: ${LOWCODER_NODE_SERVICE_SECRET_SALT:lowcodersalt}
+    is-encrypted: ${LOWCODER_NODE_SERVICE_ENCRYPTED:false}
   max-query-request-size: ${LOWCODER_MAX_REQUEST_SIZE:20m}
   max-query-response-size: ${LOWCODER_MAX_REQUEST_SIZE:20m}
   max-upload-size: ${LOWCODER_MAX_REQUEST_SIZE:20m}
@@ -129,4 +132,4 @@ management:
     redis:
       enabled: true
     diskspace:
-      enabled: false
+      enabled: false
\ No newline at end of file
diff --git a/server/node-service/src/controllers/plugins.ts b/server/node-service/src/controllers/plugins.ts
index e20a109b3..d763586ce 100644
--- a/server/node-service/src/controllers/plugins.ts
+++ b/server/node-service/src/controllers/plugins.ts
@@ -3,6 +3,23 @@ import { Request, Response } from "express";
 import _ from "lodash";
 import { Config } from "lowcoder-sdk/dataSource";
 import * as pluginServices from "../services/plugin";
+// Add import for decryption utility
+import { decryptString } from "../utils/encryption"; // <-- implement this utility as needed
+
+async function getDecryptedBody(req: Request): Promise<any> {
+  if (req.headers["x-encrypted"]) {
+    // Assume body is a raw encrypted string, decrypt and parse as JSON
+    const encrypted = typeof req.body === "string" ? req.body : req.body?.toString?.();
+    if (!encrypted) throw badRequest("Missing encrypted body");
+    const decrypted = await decryptString(encrypted);
+    try {
+      return JSON.parse(decrypted);
+    } catch (e) {
+      throw badRequest("Failed to parse decrypted body as JSON");
+    }
+  }
+  return req.body;
+}
 
 export async function listPlugins(req: Request, res: Response) {
   let ids = req.query["id"] || [];
@@ -15,12 +32,10 @@ export async function listPlugins(req: Request, res: Response) {
 }
 
 export async function runPluginQuery(req: Request, res: Response) {
-  const { pluginName, dsl, context, dataSourceConfig } = req.body;
+  const body = await getDecryptedBody(req);
+  const { pluginName, dsl, context, dataSourceConfig } = body;
   const ctx = pluginServices.getPluginContext(req);
 
-
-  // console.log("pluginName: ", pluginName, "dsl: ", dsl, "context: ", context, "dataSourceConfig: ", dataSourceConfig, "ctx: ", ctx);
-
   const result = await pluginServices.runPluginQuery(
     pluginName,
     dsl,
@@ -32,7 +47,8 @@ export async function runPluginQuery(req: Request, res: Response) {
 }
 
 export async function validatePluginDataSourceConfig(req: Request, res: Response) {
-  const { pluginName, dataSourceConfig } = req.body;
+  const body = await getDecryptedBody(req);
+  const { pluginName, dataSourceConfig } = body;
   const ctx = pluginServices.getPluginContext(req);
   const result = await pluginServices.validatePluginDataSourceConfig(
     pluginName,
@@ -50,10 +66,11 @@ type GetDynamicDefReqBody = {
 
 export async function getDynamicDef(req: Request, res: Response) {
   const ctx = pluginServices.getPluginContext(req);
-  if (!Array.isArray(req.body)) {
+  const body = await getDecryptedBody(req);
+  if (!Array.isArray(body)) {
     throw badRequest("request body is not a valid array");
   }
-  const fields = req.body as GetDynamicDefReqBody;
+  const fields = body as GetDynamicDefReqBody;
   const result: Config[] = [];
   for (const item of fields) {
     const def = await pluginServices.getDynamicConfigDef(
diff --git a/server/node-service/src/server.ts b/server/node-service/src/server.ts
index 124c8d1e5..793161ef5 100644
--- a/server/node-service/src/server.ts
+++ b/server/node-service/src/server.ts
@@ -9,6 +9,7 @@ import { collectDefaultMetrics } from "prom-client";
 import apiRouter from "./routes/apiRouter";
 import systemRouter from "./routes/systemRouter";
 import cors, { CorsOptions } from "cors";
+import bodyParser from "body-parser";
 collectDefaultMetrics();
 
 const prefix = "/node-service";
@@ -32,6 +33,15 @@ router.use(morgan("dev"));
 /** Parse the request */
 router.use(express.urlencoded({ extended: false }));
 
+/** Custom middleware: use raw body for encrypted requests */
+router.use((req, res, next) => {
+  if (req.headers["x-encrypted"]) {
+    bodyParser.text({ type: "*/*" })(req, res, next);
+  } else {
+    bodyParser.json()(req, res, next);
+  }
+});
+
 /** Takes care of JSON data */
 router.use(
   express.json({
diff --git a/server/node-service/src/utils/encryption.ts b/server/node-service/src/utils/encryption.ts
new file mode 100644
index 000000000..2240a6571
--- /dev/null
+++ b/server/node-service/src/utils/encryption.ts
@@ -0,0 +1,42 @@
+import { createDecipheriv, pbkdf2Sync } from "crypto";
+import { badRequest } from "../common/error";
+
+// Spring's Encryptors.text uses AES-256-CBC with PBKDF2 (HmacSHA1, 1024 iterations).
+const ALGORITHM = "aes-256-cbc";
+const KEY_LENGTH = 32; // 256 bits
+const IV_LENGTH = 16;  // 128 bits
+const ITERATIONS = 1024;
+const DIGEST = "sha1";
+
+// You must set these to match your Java config:
+const PASSWORD = process.env.LOWCODER_NODE_SERVICE_SECRET || "lowcoderpwd";
+const SALT_HEX = process.env.LOWCODER_NODE_SERVICE_SECRET_SALT || "lowcodersalt";
+
+/**
+ * Derive key from password and salt using PBKDF2WithHmacSHA1 (Spring's default).
+ */
+function deriveKey(password: string, saltHex: string): Buffer {
+  const salt = Buffer.from(saltHex, "utf8");
+  return pbkdf2Sync(password, salt, ITERATIONS, KEY_LENGTH, DIGEST);
+}
+
+/**
+ * Decrypt a string encrypted by Spring's Encryptors.text.
+ */
+export async function decryptString(encrypted: string): Promise<string> {
+  try {
+    // Spring's format: hex(salt) + encryptedHex(IV + ciphertext)
+    const key = deriveKey(PASSWORD, SALT_HEX);
+
+    const encryptedBuf = Buffer.from(encrypted, "hex");
+    const iv = encryptedBuf.slice(0, IV_LENGTH);
+    const ciphertext = encryptedBuf.slice(IV_LENGTH);
+
+    const decipher = createDecipheriv(ALGORITHM, key, iv);
+    let decrypted = decipher.update(ciphertext, undefined, "utf8");
+    decrypted += decipher.final("utf8");
+    return decrypted;
+  } catch (e) {
+    throw badRequest("Failed to decrypt string");
+  }
+}
\ No newline at end of file