From f47d96194754dadac4fbdaec272a378e05b01985 Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Mon, 21 Sep 2026 13:27:25 +0200 Subject: [PATCH] fix(web): rule pages tell non-admins that contributing is admin-only instead of failing with 403 The analyze and gather rule pages loaded the community submissions list for every role. rules/submissions is TenantAdminOrGA, so an Operator or Viewer got a 403 and a red "Failed to load your community submissions" banner on every page visit (16 denials in 24 h from two operators of one tenant). The pages now skip the list for callers who cannot read it and the contribution box says that only tenant admins can contribute and follow the tenant's submissions. Tenant admins, Global Admins and a global-scope override are unchanged; the backend policy stays as it is. Bumps the internal and .claude/commands gitlinks (platform-health marker catalog, backlog). Co-Authored-By: Claude Fable 5.1 --- .claude/commands | 2 +- internal | 2 +- .../app/analyze-rules/page.tsx | 23 +++++++++++-------- .../app/gather-rules/page.tsx | 23 +++++++++++-------- .../rules/CommunityContributionBox.tsx | 7 +++++- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/.claude/commands b/.claude/commands index 95b417d3..315ad0e7 160000 --- a/.claude/commands +++ b/.claude/commands @@ -1 +1 @@ -Subproject commit 95b417d3c70f7ec4cdf372e9432382f428ff71f7 +Subproject commit 315ad0e73645e212ec0b38a61a15bf4c73a707f6 diff --git a/internal b/internal index 148e99f2..7cb78f47 160000 --- a/internal +++ b/internal @@ -1 +1 @@ -Subproject commit 148e99f2e69bab60f39213a280f10b0f355ed4d9 +Subproject commit 7cb78f47ead7a9f36a61c5d43acb2d0fdbc9d1e6 diff --git a/src/Web/autopilot-monitor-web/app/analyze-rules/page.tsx b/src/Web/autopilot-monitor-web/app/analyze-rules/page.tsx index 764b94bf..327fd6d6 100644 --- a/src/Web/autopilot-monitor-web/app/analyze-rules/page.tsx +++ b/src/Web/autopilot-monitor-web/app/analyze-rules/page.tsx @@ -112,6 +112,9 @@ export default function AnalyzeRulesPage() { // A read-only Global Reader — and an own-tenant admin viewing a FOREIGN tenant (cross-tenant override) — // is read-only. Backend also enforces (rules write is TenantAdminOrGA, cross-tenant blocked for non-GA). const isReadOnly = !(user?.isGlobalAdmin || (user?.isTenantAdmin && !isGlobalOverride)); + // Community submissions are TenantAdminOrGA on the own tenant; only a global-scope override reads them through + // the operator list. Everyone else (Operator, Viewer) gets a hint instead of a 403 on the list call. + const submissionsAdminOnly = isReadOnly && !isGlobalOverride; // Cross-tenant write routing: in a Global Admin override the JWT-scoped rules/analyze/{id} route // resolves the tenant from the caller's token and would silently upsert into the GA's OWN tenant @@ -610,15 +613,17 @@ export default function AnalyzeRulesPage() { ) : (
{/* Community contribution: submit own custom rules for the shared pool; the list shows their status. */} - setShowSubmitModal(true)} /> - + setShowSubmitModal(true)} adminOnly={submissionsAdminOnly} /> + {!submissionsAdminOnly && ( + + )} {/* Summary Stats */}
diff --git a/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx b/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx index b8f7d2f6..179f0d32 100644 --- a/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx +++ b/src/Web/autopilot-monitor-web/app/gather-rules/page.tsx @@ -83,6 +83,9 @@ export default function GatherRulesPage() { // A read-only Global Reader — and an own-tenant admin viewing a FOREIGN tenant (cross-tenant override) — // is read-only. Backend also enforces (rules write is TenantAdminOrGA, cross-tenant blocked for non-GA). const isReadOnly = !(user?.isGlobalAdmin || (user?.isTenantAdmin && !isGlobalOverride)); + // Community submissions are TenantAdminOrGA on the own tenant; only a global-scope override reads them through + // the operator list. Everyone else (Operator, Viewer) gets a hint instead of a 403 on the list call. + const submissionsAdminOnly = isReadOnly && !isGlobalOverride; // Cross-tenant write routing: in a Global Admin override the JWT-scoped rules/gather/{id} route // resolves the tenant from the caller's token and would silently upsert into the GA's OWN tenant @@ -444,15 +447,17 @@ export default function GatherRulesPage() { )} {/* Community contribution: submit own custom rules for the shared pool; the list shows their status. */} - setShowSubmitModal(true)} /> - + setShowSubmitModal(true)} adminOnly={submissionsAdminOnly} /> + {!submissionsAdminOnly && ( + + )} {/* Summary Stats */}
diff --git a/src/Web/autopilot-monitor-web/components/rules/CommunityContributionBox.tsx b/src/Web/autopilot-monitor-web/components/rules/CommunityContributionBox.tsx index 9d8b19d7..5f67944c 100644 --- a/src/Web/autopilot-monitor-web/components/rules/CommunityContributionBox.tsx +++ b/src/Web/autopilot-monitor-web/components/rules/CommunityContributionBox.tsx @@ -6,10 +6,12 @@ import { DOCS_PATHS } from "@/lib/docsPaths"; interface CommunityContributionBoxProps { /** Undefined hides the button (read-only roles, no custom rules yet). */ onContribute?: () => void; + /** The caller's role cannot contribute at all (contributing is tenant-admin only): say so instead of just hiding the button. */ + adminOnly?: boolean; } /** The community call-to-action on the rule pages: contribute a rule here, report bugs on GitHub. */ -export function CommunityContributionBox({ onContribute }: CommunityContributionBoxProps) { +export function CommunityContributionBox({ onContribute, adminOnly }: CommunityContributionBoxProps) { return (
@@ -22,6 +24,9 @@ export function CommunityContributionBox({ onContribute }: CommunityContribution open a GitHub issue . + {adminOnly && ( + Only tenant admins can contribute rules and follow the tenant's submissions — ask an admin of your tenant to submit it. + )}

{onContribute && (