diff --git a/ui/litellm-dashboard/src/app/(dashboard)/compliance/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/compliance/page.tsx new file mode 100644 index 00000000000..c5209eb80c5 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/compliance/page.tsx @@ -0,0 +1,17 @@ +"use client"; + +import React from "react"; +import { Title, Text } from "@tremor/react"; +import PolicyComplianceTab from "@/components/UsagePage/components/PolicyComplianceTab"; + +const CompliancePage = () => { + return ( +
+ Policy & Compliance View + Monitor regulatory compliance across all AI requests + +
+ ); +}; + +export default CompliancePage; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx b/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx index a74d3c108d6..0e2d4fd8204 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx @@ -105,6 +105,8 @@ const routeFor = (slug: string): string => { return "guardrails"; case "policies": return "policies"; + case "compliance": + return "compliance"; // tools case "mcp-servers": @@ -175,6 +177,13 @@ const menuItems: MenuItemCfg[] = [ icon: , roles: [...all_admin_roles, ...internalUserRoles], }, + { + key: "29", + page: "compliance", + label: "Compliance", + icon: , + roles: all_admin_roles, + }, { key: "6", page: "teams", label: "Teams", icon: }, { key: "17", diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index ae3bd76e3cf..fbdf56789b6 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -21,6 +21,7 @@ import ModelHubTable from "@/components/AIHub/ModelHubTable"; import Navbar from "@/components/navbar"; import { getUiConfig, Organization, proxyBaseUrl, setGlobalLitellmHeaderName, getInProductNudgesCall } from "@/components/networking"; import NewUsagePage from "@/components/UsagePage/components/UsagePageView"; +import PolicyComplianceTab from "@/components/UsagePage/components/PolicyComplianceTab"; import OldTeams from "@/components/OldTeams"; import { fetchUserModels } from "@/components/organisms/create_key_button"; import Organizations, { fetchOrganizations } from "@/components/organizations"; @@ -552,6 +553,12 @@ function CreateKeyPageContent() { teams={(teams as Team[]) ?? []} organizations={(organizations as Organization[]) ?? []} /> + ) : page == "compliance" ? ( +
+
Policy & Compliance View
+
Monitor regulatory compliance across all AI requests
+ +
) : ( = { + "HR Automation Bot": [ + { + key: "1", timestamp: "2026-02-17 09:12:34", requestId: "req_8f3a1b2c", + regulation: "EU AI Act", article: "Art. 5 (Prohibited Practices)", severity: "critical", + model: "gpt-4o", virtualKey: "sk-hr-bot-prod", requestType: "LLM Call", + inputSnippet: "Based on the employee's performance score of 2.1/10 and attendance record, generate a termination letter and notify HR to proceed with dismissal...", + violationReason: "Automated decision-making on employment termination without mandatory human oversight. Art. 5(1)(c) prohibits AI systems that evaluate or classify persons based on social behavior leading to detrimental treatment.", + recommendation: "Add human-in-the-loop approval before any employment decisions. Route output to HR manager for review before action.", + }, + { + key: "2", timestamp: "2026-02-17 08:45:12", requestId: "req_2d4e6f8a", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "critical", + model: "gpt-4o", virtualKey: "sk-hr-bot-prod", requestType: "LLM Call", + inputSnippet: "Employee record: Name: John Smith, SSN: 412-55-8901, DOB: 1985-03-14, Medical leave history: 3 instances of mental health leave in 2025...", + violationReason: "Unencrypted PII (SSN, date of birth) and special category data (health records) sent to external LLM provider without data protection measures.", + recommendation: "Mask or tokenize PII before sending to LLM. Use litellm guardrails to detect and redact sensitive fields (pii_masking). Never send health data to external providers.", + }, + { + key: "3", timestamp: "2026-02-16 14:22:08", requestId: "req_9c1d3e5f", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", + model: "claude-3-5-sonnet", virtualKey: "sk-hr-bot-prod", requestType: "LLM Call", + inputSnippet: "Analyser le dossier de Marie Dupont: adresse 12 rue de la Paix Paris, numero secu 2 85 03 75 108 042 15, evaluations de performance 2024-2025...", + violationReason: "French national ID number (numero de securite sociale) and home address transmitted to LLM without consent or encryption.", + recommendation: "Enable PII guardrail for French ID patterns. Require explicit consent before processing employee evaluations with AI.", + }, + { + key: "4", timestamp: "2026-02-16 11:03:55", requestId: "req_4b6c8d0e", + regulation: "EU AI Act", article: "Art. 5 (Prohibited Practices)", severity: "critical", + model: "gpt-4o", virtualKey: "sk-hr-bot-prod", requestType: "LLM Call", + inputSnippet: "Rank all employees in the engineering department by: productivity score, peer review sentiment, Slack activity metrics, badge-in frequency. Flag bottom 10% for performance improvement plan...", + violationReason: "Social scoring of employees using behavioral surveillance data (Slack activity, badge-in frequency). This constitutes prohibited social scoring under Art. 5(1)(c).", + recommendation: "Remove behavioral surveillance inputs. Performance reviews must use only job-relevant, transparent criteria with employee awareness.", + }, + { + key: "5", timestamp: "2026-02-15 16:47:21", requestId: "req_7a9b1c3d", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", + model: "gpt-4o", virtualKey: "sk-hr-bot-prod", requestType: "LLM Call", + inputSnippet: "Summarize sick leave patterns for the following employees and flag anyone with >5 days mental health leave: [list of 47 employees with full medical records]...", + violationReason: "Bulk processing of health data (special category under Art. 9 GDPR) without explicit consent or legitimate basis. Data sent to US-based provider without adequate safeguards.", + recommendation: "Health data processing requires explicit employee consent per Art. 9(2)(a). Aggregate and anonymize before any AI analysis. Consider EU-hosted model.", + }, + ], + "Internal Doc Search": [ + { + key: "1", timestamp: "2026-02-17 10:05:18", requestId: "req_1e2f3a4b", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", + model: "text-embedding-3-small", virtualKey: "sk-docsearch-prod", requestType: "LLM Call", + inputSnippet: "Search query: 'Find all contracts mentioning employee salary bands for Sarah Chen, Michael Rodriguez, and compensation packages above 200k'...", + violationReason: "Search query retrieves and exposes individual salary data (personal data) without access controls or legitimate business need verification.", + recommendation: "Add role-based access controls to document search. Salary data queries should require manager-level permissions and audit logging.", + }, + { + key: "2", timestamp: "2026-02-16 09:33:41", requestId: "req_5c6d7e8f", + regulation: "MCP Unregistered", article: "MCP Unregistered Server", severity: "medium", + model: "gpt-4o", virtualKey: "sk-docsearch-prod", requestType: "MCP Tool Call", + inputSnippet: "Tool call to 'internal-search-v2' server at endpoint https://search-staging.internal:8443/query — server not found in MCP registry...", + violationReason: "MCP tool call routed to unregistered server 'internal-search-v2'. This server is not in the approved MCP registry and has not been security-reviewed.", + recommendation: "Register 'internal-search-v2' in the MCP server registry via Settings > MCP Servers. Ensure security review is completed before production use.", + }, + { + key: "3", timestamp: "2026-02-15 15:22:09", requestId: "req_9a0b1c2d", + regulation: "GDPR", article: "Art. 32 (Data Protection)", severity: "high", + model: "text-embedding-3-small", virtualKey: "sk-docsearch-prod", requestType: "LLM Call", + inputSnippet: "Recherche: 'dossiers medicaux employes site Lyon, certificats arret maladie 2025, notes medecin du travail'...", + violationReason: "Search query targets medical records (special category data). Embedding model processes sensitive health information without adequate protection.", + recommendation: "Exclude medical/health document collections from general search index. Create separate, access-controlled index with explicit consent requirements.", + }, + ], + "Contract Analyzer": [ + { + key: "1", timestamp: "2026-02-17 07:55:02", requestId: "req_3d4e5f6a", + regulation: "EU AI Act", article: "Art. 9 (Risk Management)", severity: "high", + model: "claude-3-5-sonnet", virtualKey: "sk-contracts-prod", requestType: "LLM Call", + inputSnippet: "Analyze this $4.2M vendor contract and recommend whether to approve or reject. Key terms: liability cap, SLA penalties, data processing addendum. Auto-approve if risk score < 0.3...", + violationReason: "High-risk AI decision (contract approval >$1M) without mandatory risk assessment documentation. Art. 9 requires documented risk management for high-value automated decisions.", + recommendation: "Contracts above threshold must go through documented risk assessment. Add human approval step for AI-recommended contract decisions above $1M.", + }, + { + key: "2", timestamp: "2026-02-16 13:18:45", requestId: "req_7b8c9d0e", + regulation: "GDPR", article: "Art. 38 (Audit Records)", severity: "medium", + model: "gpt-4o", virtualKey: "sk-contracts-prod", requestType: "LLM Call", + inputSnippet: "Extract all personal data subjects mentioned in the attached data processing agreement. List names, roles, and data categories processed...", + violationReason: "Contract analysis extracting personal data without maintaining required audit records. Art. 38 requires DPO notification and logging for data subject identification activities.", + recommendation: "Enable detailed audit logging for all contract analysis requests involving personal data. Notify DPO when data subject identification is performed.", + }, + ], + "Customer Support": [ + { + key: "1", timestamp: "2026-02-14 11:22:33", requestId: "req_1f2a3b4c", + regulation: "EU AI Act", article: "Art. 12 (Transparency)", severity: "medium", + model: "gpt-4o-mini", virtualKey: "sk-support-prod", requestType: "LLM Call", + inputSnippet: "Customer asked: 'Am I speaking with a real person?' System prompt instructs: 'You are a helpful customer service representative named Alex. Never reveal you are an AI.'...", + violationReason: "AI system instructed to conceal its nature when directly asked by user. Art. 12 requires AI systems to be transparent about their non-human nature.", + recommendation: "Update system prompt to disclose AI nature when asked. Add standard disclosure: 'I'm an AI assistant powered by [company]. I can connect you with a human agent.'", + }, + { + key: "2", timestamp: "2026-02-13 09:44:17", requestId: "req_5d6e7f8a", + regulation: "EU AI Act", article: "Art. 12 (Transparency)", severity: "medium", + model: "gpt-4o-mini", virtualKey: "sk-support-prod", requestType: "LLM Call", + inputSnippet: "Le client demande: 'Est-ce que je parle a un humain ou a un robot?' Instruction systeme: 'Repondre comme un agent humain, ne pas mentionner l'IA'...", + violationReason: "Same transparency violation in French-language support channel. Customer explicitly asked if speaking to AI and system is instructed to deny it.", + recommendation: "Apply the same transparency fix across all language channels. System prompt must allow AI self-identification in all supported languages.", + }, + ], + "Platform Chatbot": [ + { + key: "1", timestamp: "2026-02-12 16:08:52", requestId: "req_9b0c1d2e", + regulation: "EU AI Act", article: "Art. 12 (Transparency)", severity: "medium", + model: "gpt-4o-mini", virtualKey: "sk-chatbot-prod", requestType: "Agent Call", + inputSnippet: "Chatbot greeting: 'Hi! I'm your personal assistant. How can I help you today?' — no AI disclosure in greeting or system prompt...", + violationReason: "Public-facing chatbot does not identify itself as an AI system at any point in the interaction. Art. 12 requires clear disclosure before or at the start of interaction.", + recommendation: "Add AI disclosure to chatbot greeting: 'Hi! I'm an AI assistant for [Platform]. How can I help?' Also add disclosure in the chat widget UI.", + }, + ], +}; + +// ---- Component ---- + +const PolicyComplianceTab: React.FC = () => { + const initialFromDate = useMemo(() => new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), []); + const initialToDate = useMemo(() => new Date(), []); + const [dateValue, setDateValue] = useState({ + from: initialFromDate, + to: initialToDate, + }); + const [teamPageSize, setTeamPageSize] = useState(5); + const [drawerOpen, setDrawerOpen] = useState(false); + const [selectedTeam, setSelectedTeam] = useState(null); + + const violationCount = (val: number) => { + if (val === 0) return {val}; + if (val > 5) return {val}; + return {val}; + }; + + const riskBadgeStyle = (risk: string): React.CSSProperties => { + const base: React.CSSProperties = { + display: "inline-block", + padding: "1px 8px", + borderRadius: "10px", + fontSize: "12px", + fontWeight: 500, + lineHeight: "20px", + }; + switch (risk) { + case "HIGH": return { ...base, backgroundColor: "#fef2f2", color: "#dc2626" }; + case "MED": return { ...base, backgroundColor: "#fff7ed", color: "#d97706" }; + case "LOW": return { ...base, backgroundColor: "#f0fdf4", color: "#16a34a" }; + default: return { ...base, backgroundColor: "#f3f4f6", color: "#6b7280" }; + } + }; + + const columnHeader = (title: string, sub: string) => ( +
+
{title}
+
{sub}
+
+ ); + + const teamColumns: ColumnsType = [ + { + title: "Team / Use Case", + dataIndex: "team", + key: "team", + render: (text: string) => ( + { setSelectedTeam(text); setDrawerOpen(true); }} + className="text-blue-600 hover:text-blue-800 cursor-pointer" + > + {text} + + ), + }, + { + title: columnHeader("EU AI Act", "(violations)"), + dataIndex: "euAiAct", + key: "euAiAct", + render: violationCount, + }, + { + title: columnHeader("GDPR", "(violations)"), + dataIndex: "gdpr", + key: "gdpr", + render: violationCount, + }, + { + title: columnHeader("MCP Unregistered", "(violations)"), + dataIndex: "mcpUnregistered", + key: "mcpUnregistered", + render: violationCount, + }, + { + title: "Risk", + dataIndex: "risk", + key: "risk", + render: (risk: string) => {risk}, + }, + ]; + + const severityBadge = (severity: string) => { + const styles: Record = { + critical: { backgroundColor: "#fef2f2", color: "#dc2626", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, + high: { backgroundColor: "#fff7ed", color: "#d97706", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, + medium: { backgroundColor: "#fefce8", color: "#a16207", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, + }; + return {severity.toUpperCase()}; + }; + + const regulationBadge = (reg: string) => { + const styles: Record = { + "EU AI Act": { backgroundColor: "#eef2ff", color: "#4338ca", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, + "GDPR": { backgroundColor: "#f0fdf4", color: "#15803d", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, + "MCP Unregistered": { backgroundColor: "#fff7ed", color: "#c2410c", padding: "1px 8px", borderRadius: "10px", fontSize: "12px", fontWeight: 500 }, + }; + return {reg}; + }; + + const renderViolationCard = (log: DrillDownLog) => ( + + {/* Header row */} +
+
+ {severityBadge(log.severity)} + {regulationBadge(log.regulation)} + {log.article} +
+ {log.timestamp} +
+ + {/* Meta row */} +
+ Request: {log.requestId} + Model: {log.model} + Key: {log.virtualKey} + Type: {log.requestType} +
+ + {/* Input snippet */} +
+
Input that triggered violation
+
+ {log.inputSnippet} +
+
+ + {/* Why it failed */} +
+
Why this failed
+
+ {log.violationReason} +
+
+ + {/* How to fix */} +
+
Recommended fix
+
+ {log.recommendation} +
+
+
+ ); + + return ( +
+ {/* Date Picker */} +
+ +
+ + {/* KPI Cards */} + + + Compliance Metrics + + + Total Requests + + {MOCK_KPI.totalRequests.toLocaleString()} + + + + EU AI Act Violations + + {MOCK_KPI.euAiActViolations} + + + + GDPR Violations + + {MOCK_KPI.gdprViolations} + + + + MCP Unregistered Calls + + {MOCK_KPI.mcpUnregisteredCalls} + + + + Compliant Requests + + {MOCK_KPI.compliantRequests.toLocaleString()} + + + + + + + {/* Daily Violations Chart */} + + + Daily Violations + + + + + {/* Two side-by-side: Teams table + Regulation articles chart */} + + + +
+ Top Teams by Violations + setTeamPageSize(value as number)} + /> +
+ + + + + + + Violations by Regulation Article + + + + + + {/* Violations by Request Type */} + + + Violations by Request Type + + + + + {/* Drill-down Drawer */} + setDrawerOpen(false)} + > + {selectedTeam && ( +
+ {/* Summary banner */} + {(() => { + const logs = MOCK_DRILL_DOWN[selectedTeam] || []; + const critical = logs.filter(l => l.severity === "critical").length; + const high = logs.filter(l => l.severity === "high").length; + const medium = logs.filter(l => l.severity === "medium").length; + return ( +
+ {logs.length} violations total + {critical > 0 && {critical} critical} + {high > 0 && {high} high} + {medium > 0 && {medium} medium} +
+ ); + })()} + {(MOCK_DRILL_DOWN[selectedTeam] || []).map(renderViolationCard)} +
+ )} +
+ + ); +}; + +export default PolicyComplianceTab; diff --git a/ui/litellm-dashboard/src/components/leftnav.tsx b/ui/litellm-dashboard/src/components/leftnav.tsx index 150be88de21..4024d6ca62e 100644 --- a/ui/litellm-dashboard/src/components/leftnav.tsx +++ b/ui/litellm-dashboard/src/components/leftnav.tsx @@ -154,6 +154,13 @@ const menuGroups: MenuGroup[] = [ label: "Logs", icon: , }, + { + key: "compliance", + page: "compliance", + label: "Compliance", + icon: , + roles: all_admin_roles, + }, ], }, {