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 b88bc2fb8 fix(acl): report unrestricted account sources in risk 
diagnostics (#7291)
b88bc2fb8 is described below

commit b88bc2fb8268b1b6c5a98b6d7eb746e82e9a5525
Author: coder999o <[email protected]>
AuthorDate: Mon Oct 26 20:09:00 2026 +0800

    fix(acl): report unrestricted account sources in risk diagnostics (#7291)
    
    * fix(acl): report unrestricted account sources in risk diagnostics
---
 web/src/pages/instance/__tests__/AclPage.test.tsx | 48 ++++++++++++-----------
 web/src/utils/aclRiskDiagnostics.test.ts          | 31 +++++++++++++++
 web/src/utils/aclRiskDiagnostics.ts               |  7 +++-
 3 files changed, 62 insertions(+), 24 deletions(-)

diff --git a/web/src/pages/instance/__tests__/AclPage.test.tsx 
b/web/src/pages/instance/__tests__/AclPage.test.tsx
index 6be2f6fca..cfb3c8182 100644
--- a/web/src/pages/instance/__tests__/AclPage.test.tsx
+++ b/web/src/pages/instance/__tests__/AclPage.test.tsx
@@ -213,18 +213,20 @@ describe('ACL page', () => {
       },
     ]);
     vi.mocked(aclService.listAclRules).mockResolvedValue({
-      items: [{
-        id: 21,
-        principal: 'directory-only-user',
-        resource: 'orders',
-        resourceType: 'Topic',
-        resourcePattern: 'LITERAL',
-        actions: ['PUB'],
-        decision: 'ALLOW',
-        scope: 'cluster',
-        aclVersion: 2,
-        gmtCreate: '2026-07-23T00:00:00Z',
-      }],
+      items: [
+        {
+          id: 21,
+          principal: 'directory-only-user',
+          resource: 'orders',
+          resourceType: 'Topic',
+          resourcePattern: 'LITERAL',
+          actions: ['PUB'],
+          decision: 'ALLOW',
+          scope: 'cluster',
+          aclVersion: 2,
+          gmtCreate: '2026-07-23T00:00:00Z',
+        },
+      ],
       total: 1,
       page: 1,
       size: 20,
@@ -1100,14 +1102,16 @@ describe('ACL page', () => {
         aclEnabled: true,
         aclVersion: 'ACL 2.0',
         globalWhiteRemoteAddresses: [],
-        accounts: [{
-          accessKey: 'cluster-a-account',
-          admin: false,
-          defaultTopicPerm: 'DENY',
-          defaultGroupPerm: 'DENY',
-          topicPerms: [],
-          groupPerms: [],
-        }],
+        accounts: [
+          {
+            accessKey: 'cluster-a-account',
+            admin: false,
+            defaultTopicPerm: 'DENY',
+            defaultGroupPerm: 'DENY',
+            topicPerms: [],
+            groupPerms: [],
+          },
+        ],
         accountCount: 1,
       })
       .mockRejectedValueOnce(new Error('cluster-b unavailable'));
@@ -1118,7 +1122,7 @@ describe('ACL page', () => {
     const examineButton = screen.getByRole('button', { name: /检\s*查\s*配\s*置/ 
});
     await user.type(clusterInput, 'cluster-a');
     await user.click(examineButton);
-    expect(await screen.findByText('cluster-a-account')).toBeInTheDocument();
+    expect(await 
screen.findAllByText('cluster-a-account')).not.toHaveLength(0);
     expect(screen.getByText('集群: cluster-a')).toBeInTheDocument();
 
     await user.clear(clusterInput);
@@ -1127,7 +1131,7 @@ describe('ACL page', () => {
     await waitFor(() => 
expect(aclService.examineBrokerClusterAclConfig).toHaveBeenCalledTimes(2));
     await waitFor(() => 
expect(examineButton).not.toHaveClass('ant-btn-loading'));
 
-    expect(screen.queryByText('cluster-a-account')).not.toBeInTheDocument();
+    expect(screen.queryAllByText('cluster-a-account')).toHaveLength(0);
     expect(screen.queryByText('集群: cluster-a')).not.toBeInTheDocument();
   });
 
diff --git a/web/src/utils/aclRiskDiagnostics.test.ts 
b/web/src/utils/aclRiskDiagnostics.test.ts
index 210a7f4ab..7d53b5b9e 100644
--- a/web/src/utils/aclRiskDiagnostics.test.ts
+++ b/web/src/utils/aclRiskDiagnostics.test.ts
@@ -61,6 +61,37 @@ describe('ACL risk diagnostics', () => {
     ]);
   });
 
+  it.each([undefined, null, '', ' \t '])(
+    'reports an unrestricted account source for %jTest',
+    (whiteRemoteAddress) => {
+      const diagnostics = analyzeAclRisk(config({ accounts: [account({ 
whiteRemoteAddress })] }));
+
+      expect(diagnostics.status).toBe('critical');
+      expect(diagnostics.summary.broadWhitelistCount).toBe(1);
+      expect(diagnostics.issues).toEqual([
+        expect.objectContaining({
+          code: 'BROAD_ACCOUNT_WHITELIST',
+          severity: 'critical',
+          evidence: ['<empty>'],
+        }),
+      ]);
+    },
+  );
+
+  it('reports an unrestricted administrator despite deny-by-default 
permissionsTest', () => {
+    const diagnostics = analyzeAclRisk(
+      config({ accounts: [account({ admin: true, whiteRemoteAddress: '' })] }),
+    );
+
+    expect(diagnostics.status).toBe('critical');
+    expect(diagnostics.score).toBeLessThan(100);
+    expect(diagnostics.summary.broadWhitelistCount).toBe(1);
+    expect(diagnostics.issues.map((item) => item.code)).toEqual(
+      expect.arrayContaining(['BROAD_ACCOUNT_WHITELIST', 
'ADMIN_WITH_BROAD_ACCESS']),
+    );
+    expect(diagnostics.issues.some((item) => item.code === 
'BROAD_GLOBAL_WHITELIST')).toBe(false);
+  });
+
   it('flags disabled ACL and missing accounts as critical risks', () => {
     const diagnostics = analyzeAclRisk(
       config({
diff --git a/web/src/utils/aclRiskDiagnostics.ts 
b/web/src/utils/aclRiskDiagnostics.ts
index 17a47ab57..74efa0651 100644
--- a/web/src/utils/aclRiskDiagnostics.ts
+++ b/web/src/utils/aclRiskDiagnostics.ts
@@ -228,8 +228,11 @@ const collectDuplicateAccessKeys = (accounts: 
PlainAccessConfig[]): Set<string>
   return duplicates;
 };
 
-const accountWhitelistClass = (account: PlainAccessConfig): WhitelistClass =>
-  classifyWhitelist(splitWhitelist(account.whiteRemoteAddress));
+const accountWhitelistClass = (account: PlainAccessConfig): WhitelistClass => {
+  const addresses = splitWhitelist(account.whiteRemoteAddress);
+  // An empty account whitelist imposes no source restriction, unlike an empty 
global trust list.
+  return addresses.length === 0 ? 'open' : classifyWhitelist(addresses);
+};
 
 const addDefaultPermissionIssues = (
   issues: AclRiskIssue[],

Reply via email to