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

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new dff6876d6 fix(client): constrain diagnostics to registered nameservers 
(#4534)
dff6876d6 is described below

commit dff6876d60d896d9be5507061294299395b54f12
Author: zmuxuny <[email protected]>
AuthorDate: Mon Sep 21 21:01:16 2026 +0800

    fix(client): constrain diagnostics to registered nameservers (#4534)
    
    `ClientService.listConnectionsAt` handed the caller-supplied `namesrvAddr` 
straight to the provider after a `trim()`, so anyone with read access to the 
Clients API could make Studio open an outbound RocketMQ connection to an 
arbitrary endpoint that was never registered — an SSRF surface that the 
registry boundary was supposed to close.
    
    The address now goes through 
`NameserverRegistryService.requireRegisteredAddress`, which normalizes it with 
`NamesrvAddrParser.normalize` and requires an exact `selectCount` match on 
`rmq_nameserver.namesrv_addr`, answering `BusinessException(404)` when there is 
none; a malformed address is rejected by the parser before the database is 
consulted. `listConnectionsAt` passes the normalized value on to 
`findConnectionsAt`. The method has a single caller in `ClientController`, and 
the web [...]
    
    Fixes #4502
---
 .../studio/cluster/client/ClientService.java       |  5 +++-
 .../nameserver/NameserverRegistryService.java      | 10 ++++++++
 .../studio/cluster/client/ClientServiceTest.java   | 28 ++++++++++++++++++---
 .../nameserver/NameserverRegistryServiceTest.java  | 29 ++++++++++++++++++++++
 4 files changed, 67 insertions(+), 5 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/client/ClientService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/client/ClientService.java
index 76c734d19..88594c124 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/client/ClientService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/client/ClientService.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.studio.cluster.client;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.cluster.nameserver.NameserverRegistryService;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 
@@ -30,6 +31,7 @@ import java.util.List;
 public class ClientService {
 
     private final ClientProvider clientProvider;
+    private final NameserverRegistryService nameserverRegistryService;
 
     public List<ClientConnectionVO> listConnections(String instanceId, String 
clusterId, String type) {
         log.info("Listing client connections, instanceId={}, clusterId={}, 
type={}", instanceId, clusterId, type);
@@ -41,7 +43,8 @@ public class ClientService {
         if (!StringUtils.hasText(namesrvAddr)) {
             throw new BusinessException(400, "namesrvAddr is required");
         }
-        return clientProvider.findConnectionsAt(namesrvAddr.trim(), 
normalizeFilter(clusterId), normalizeFilter(type));
+        String registeredAddress = 
nameserverRegistryService.requireRegisteredAddress(namesrvAddr);
+        return clientProvider.findConnectionsAt(registeredAddress, 
normalizeFilter(clusterId), normalizeFilter(type));
     }
 
     private String requireInstanceId(String instanceId) {
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
index 908cb4095..516f6bac0 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryService.java
@@ -39,6 +39,16 @@ public class NameserverRegistryService {
                 .toList();
     }
 
+    public String requireRegisteredAddress(String rawAddress) {
+        String normalized = NamesrvAddrParser.normalize(rawAddress);
+        Long matches = nameserverMapper.selectCount(new 
QueryWrapper<RmqNameserver>()
+                .eq("namesrv_addr", normalized));
+        if (matches == null || matches == 0L) {
+            throw new BusinessException(404, "NameServer endpoint is not 
registered: " + normalized);
+        }
+        return normalized;
+    }
+
     public NameserverRegistryVO create(CreateNameserverRegistryDTO command) {
         String name = normalizeName(command.getName());
         Long existing = nameserverMapper.selectCount(new 
QueryWrapper<RmqNameserver>()
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/client/ClientServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/client/ClientServiceTest.java
index 7ea32724d..78fef0525 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/client/ClientServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/client/ClientServiceTest.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.studio.cluster.client;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.cluster.nameserver.NameserverRegistryService;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -37,6 +38,9 @@ class ClientServiceTest {
     @Mock
     private ClientProvider clientProvider;
 
+    @Mock
+    private NameserverRegistryService nameserverRegistryService;
+
     @InjectMocks
     private ClientService clientService;
 
@@ -74,15 +78,31 @@ class ClientServiceTest {
     }
 
     @Test
-    void listConnectionsAtShouldTrimAndDelegateToProvider() {
-        when(clientProvider.findConnectionsAt("10.0.1.31:9876", 
"DefaultCluster", null))
+    void listConnectionsAtShouldUseRegisteredNormalizedAddressTest() {
+        when(nameserverRegistryService.requireRegisteredAddress(" NS1:9876 ; 
ns2:9876 "))
+                .thenReturn("ns1:9876,ns2:9876");
+        when(clientProvider.findConnectionsAt("ns1:9876,ns2:9876", 
"DefaultCluster", null))
                 .thenReturn(List.of());
 
         List<ClientConnectionVO> result =
-                clientService.listConnectionsAt(" 10.0.1.31:9876 ", " 
DefaultCluster ", " ");
+                clientService.listConnectionsAt(" NS1:9876 ; ns2:9876 ", " 
DefaultCluster ", " ");
 
         assertThat(result).isEmpty();
-        verify(clientProvider).findConnectionsAt("10.0.1.31:9876", 
"DefaultCluster", null);
+        verify(nameserverRegistryService).requireRegisteredAddress(" NS1:9876 
; ns2:9876 ");
+        verify(clientProvider).findConnectionsAt("ns1:9876,ns2:9876", 
"DefaultCluster", null);
+    }
+
+    @Test
+    void listConnectionsAtShouldRejectUnregisteredAddressBeforeProviderTest() {
+        
when(nameserverRegistryService.requireRegisteredAddress("10.0.9.9:9876"))
+                .thenThrow(new BusinessException(404,
+                        "NameServer endpoint is not registered: 
10.0.9.9:9876"));
+
+        assertThatThrownBy(() -> 
clientService.listConnectionsAt("10.0.9.9:9876", null, null))
+                .isInstanceOf(BusinessException.class)
+                .satisfies(error -> assertThat(((BusinessException) 
error).getCode()).isEqualTo(404));
+
+        verifyNoInteractions(clientProvider);
     }
 
     @Test
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
index 29a50f398..c0020903e 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/nameserver/NameserverRegistryServiceTest.java
@@ -80,6 +80,35 @@ class NameserverRegistryServiceTest {
         verify(nameserverMapper).selectList(any());
     }
 
+    @Test
+    void requireRegisteredAddressShouldNormalizeBeforeLookupTest() {
+        when(nameserverMapper.selectCount(any())).thenReturn(1L);
+
+        String result = service.requireRegisteredAddress(" NS1:9876 ; ns2:9876 
");
+
+        assertThat(result).isEqualTo("ns1:9876,ns2:9876");
+        verify(nameserverMapper).selectCount(any());
+    }
+
+    @Test
+    void requireRegisteredAddressShouldRejectUnregisteredEndpointTest() {
+        when(nameserverMapper.selectCount(any())).thenReturn(0L);
+
+        assertThatThrownBy(() -> service.requireRegisteredAddress(" NS1:9876 ; 
ns2:9876 "))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("NameServer endpoint is not registered: 
ns1:9876,ns2:9876")
+                .satisfies(error -> assertThat(((BusinessException) 
error).getCode()).isEqualTo(404));
+    }
+
+    @Test
+    void 
requireRegisteredAddressShouldRejectMalformedEndpointBeforeDatabaseLookupTest() 
{
+        assertThatThrownBy(() -> service.requireRegisteredAddress("ns1"))
+                .isInstanceOf(BusinessException.class)
+                .satisfies(error -> assertThat(((BusinessException) 
error).getCode()).isEqualTo(400));
+
+        verify(nameserverMapper, never()).selectCount(any());
+    }
+
     @Test
     void createShouldPersistAndReturnStoredEntryTest() {
         when(nameserverMapper.selectCount(any())).thenReturn(0L);

Reply via email to