From fe240e608e60355bc98bafb3ddd60b27b0e6dc55 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Sat, 9 May 2026 09:21:28 -0700 Subject: [PATCH] fix(ui): hide guardrail review buttons from non-admin users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The team guardrail submissions list rendered Approve/Reject buttons for non-admin users even though the backend correctly rejected the calls. Thread userRole from the page through GuardrailsPanel into TeamGuardrailsTab and gate the row-card and detail-panel review buttons on isAdmin so the UI matches the backend authorization. Defense in depth only — the backend remains the source of truth and is double-gated at both the route admin check and the explicit endpoint role check. Refs LIT-2494 --- .../src/app/(dashboard)/guardrails/page.tsx | 4 +- .../src/components/guardrails.tsx | 2 +- .../guardrails/TeamGuardrailsTab.tsx | 15 +++- .../tests/team_guardrails_tab.test.tsx | 80 +++++++++++++++++++ 4 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 ui/litellm-dashboard/tests/team_guardrails_tab.test.tsx diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx index 50cee215eb9..f3f1e79ac2a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx @@ -4,9 +4,9 @@ import GuardrailsPanel from "@/components/guardrails"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; const GuardrailsPage = () => { - const { accessToken } = useAuthorized(); + const { accessToken, userRole } = useAuthorized(); - return ; + return ; }; export default GuardrailsPage; diff --git a/ui/litellm-dashboard/src/components/guardrails.tsx b/ui/litellm-dashboard/src/components/guardrails.tsx index fee9d02d3ae..c2bc4a903a9 100644 --- a/ui/litellm-dashboard/src/components/guardrails.tsx +++ b/ui/litellm-dashboard/src/components/guardrails.tsx @@ -252,7 +252,7 @@ const GuardrailsPanel: React.FC = ({ accessToken, userRole { key: "submitted", label: "Submitted Guardrails", - children: , + children: , }, ]} /> diff --git a/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx b/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx index b03ac92ba2b..8ba26553c42 100644 --- a/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx @@ -25,6 +25,7 @@ import { import NotificationsManager from "@/components/molecules/notifications_manager"; import TeamDropdown from "@/components/common_components/team_dropdown"; import { useRegisterGuardrail } from "@/app/(dashboard)/hooks/guardrails/useRegisterGuardrail"; +import { isAdminRole } from "@/utils/roles"; type GuardrailStatus = "active" | "pending" | "rejected"; @@ -229,6 +230,7 @@ type GuardrailCardProps = { guardrail: TeamGuardrail; isSelected: boolean; isHeadersExpanded: boolean; + isAdmin: boolean; onSelect: () => void; onToggleForwardKey: () => void; onToggleHeaders: () => void; @@ -240,6 +242,7 @@ function GuardrailCard({ guardrail: g, isSelected, isHeadersExpanded, + isAdmin, onSelect, onToggleForwardKey, onToggleHeaders, @@ -304,7 +307,7 @@ function GuardrailCard({ > {isSelected ? "Close" : "Review"} - {g.status === "pending" && ( + {isAdmin && g.status === "pending" && ( <> - {g.status === "pending" && ( + {isAdmin && g.status === "pending" && (