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

wu-sheng pushed a commit to branch fix/pre-1.0-audit-followups
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit b3f72aa6b712a7ed10cf7d1509f100fda03e8f14
Author: Wu Sheng <[email protected]>
AuthorDate: Sun Jun 28 10:08:21 2026 +0800

    fix: UI RBAC verb-matcher parity with the BFF + generic 500 error bodies
    
    Two pre-1.0 correctness fixes surfaced by an audit:
    
    - The UI auth store's verb matcher diverged from the BFF's rbac/matchOne: it
      ignored the `admin` sentinel and truncated verbs to two segments. So a 
custom
      `admin` grant was hidden in the UI, and a `*:write` grant showed
      rule:write:structural controls the BFF then denies. Port matchOne exactly
      (admin sentinel, split(':', 3), sub-action equality) so the advisory UI 
gate
      agrees with the enforcing BFF.
    - The global Fastify error handler returned raw err.message to the client 
for
      every non-HttpError 500, which can carry upstream response snippets or
      endpoint details. Log it server-side and return a generic body + 
requestId;
      keep err.message only in NODE_ENV=development. HttpError messages
      (intentionally client-facing) are unchanged.
---
 apps/bff/src/server.ts    | 14 +++++++++++---
 apps/ui/src/state/auth.ts | 15 ++++++++++-----
 2 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/apps/bff/src/server.ts b/apps/bff/src/server.ts
index eba8ce9..beea85f 100644
--- a/apps/bff/src/server.ts
+++ b/apps/bff/src/server.ts
@@ -151,13 +151,21 @@ source.onChange((cfg) => {
 
 const app = Fastify({ logger: loggerOptions });
 
-app.setErrorHandler((err, _req, reply) => {
+app.setErrorHandler((err, req, reply) => {
   if (err instanceof HttpError) {
     return reply.status(err.statusCode).send({ code: err.code, message: 
err.message, details: err.details });
   }
-  const message = err instanceof Error ? err.message : 'internal error';
+  // Never leak an internal / upstream exception message to the client — it can
+  // carry upstream response snippets or endpoint details. Log it server-side,
+  // return a generic body plus the request id for correlation; dev keeps the
+  // raw message for debugging.
   reply.log.error({ err }, 'unhandled');
-  return reply.status(500).send({ code: 'internal_error', message });
+  const isDev = process.env.NODE_ENV === 'development';
+  return reply.status(500).send({
+    code: 'internal_error',
+    message: isDev && err instanceof Error ? err.message : 'internal error',
+    requestId: req.id,
+  });
 });
 
 // Baseline security headers on every response (MIME-sniff / clickjacking /
diff --git a/apps/ui/src/state/auth.ts b/apps/ui/src/state/auth.ts
index 3d5f781..161feb5 100644
--- a/apps/ui/src/state/auth.ts
+++ b/apps/ui/src/state/auth.ts
@@ -77,14 +77,19 @@ export const useAuthStore = defineStore('auth', () => {
     user.value = null;
   }
 
+  // Mirrors the BFF's matchOne (apps/bff/src/rbac/verbs.ts) exactly. This UI 
gate
+  // is advisory — the BFF enforces — but it must agree, or it hides custom 
`admin`
+  // grants and shows three-segment controls (e.g. rule:write:structural) that 
a
+  // two-segment grant like `*:write` does not actually carry and the BFF 
denies.
   function hasVerb(verb: string): boolean {
     const grants = user.value?.verbs ?? [];
     for (const g of grants) {
-      if (g === '*' || g === verb) return true;
-      const [ga, gact] = g.split(':', 2);
-      const [ra, ract] = verb.split(':', 2);
-      if (gact === '*' && ga === ra) return true;
-      if (ga === '*' && gact === ract) return true;
+      if (g === '*' || g === 'admin' || g === verb) return true;
+      const [ga, gact, gsub] = g.split(':', 3);
+      const [ra, ract, rsub] = verb.split(':', 3);
+      if (ga === ra && gact === '*') return true;
+      if (ga === '*' && gact === ract && (gsub ?? '') === (rsub ?? '')) return 
true;
+      if (ga === ra && gact === ract && (gsub ?? '') === (rsub ?? '')) return 
true;
     }
     return false;
   }

Reply via email to