diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 83ae78afd1a..599ab205e6d 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -620,6 +620,13 @@ class SPAStaticFiles(StaticFiles): last_segment = path.rstrip("/").rsplit("/", 1)[-1] return "." not in last_segment + def _bounded_shell(self, relative: str) -> Optional[str]: + base = os.path.realpath(str(self.directory)) + candidate = os.path.realpath(os.path.join(base, relative)) + if candidate != base and not candidate.startswith(base + os.sep): + return None + return relative if os.path.isfile(candidate) else None + def _spa_shell(self, path: str) -> Optional[str]: trimmed = path.strip("/") segments = trimmed.split("/") if trimmed else [] @@ -630,11 +637,10 @@ class SPAStaticFiles(StaticFiles): if parent else f"{_SPA_SHELL_PLACEHOLDER_SEGMENT}/index.html" ) - if os.path.isfile(os.path.join(str(self.directory), placeholder)): - return placeholder - if os.path.isfile(os.path.join(str(self.directory), "index.html")): - return "index.html" - return None + shell = self._bounded_shell(placeholder) + if shell is not None: + return shell + return self._bounded_shell("index.html") async def _try_original(self, path: str, scope: StarletteScope) -> Optional[StarletteResponse]: try: diff --git a/ui/litellm-dashboard/src/app/(dashboard)/api-keys/[keyid]/KeyDetailPage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/api-keys/[keyid]/KeyDetailPage.tsx index 1c7b7d3001b..56def7f6a6d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/api-keys/[keyid]/KeyDetailPage.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/api-keys/[keyid]/KeyDetailPage.tsx @@ -6,6 +6,8 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import LoadingScreen from "@/components/common_components/LoadingScreen"; import KeyInfoView from "@/components/templates/key_info_view"; import { migratedHref } from "@/utils/migratedPages"; +import { ArrowLeftOutlined } from "@ant-design/icons"; +import { Button } from "antd"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -22,19 +24,24 @@ export default function KeyDetailPage() { const { data: keyData, isPending } = useKeyInfo(keyId); const { data: teams } = useAllTeams(); + const backToKeys = () => router.push(migratedHref("api-keys")); + if (authLoading || !isAuthorized) { return ; } if (!keyId || isPending) { return ; } + if (!keyData) { + return ( +
+ +

Key not found

+
+ ); + } - return ( - router.push(migratedHref("api-keys"))} - /> - ); + return ; } diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx index cd97f6b851d..78b0f9e02cf 100644 --- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx @@ -50,13 +50,6 @@ const PREMIUM_METADATA_FIELDS = ["policies", "guardrails", "prompts", "tags", "a const isEmptyValue = (v: unknown): boolean => v == null || (Array.isArray(v) && v.length === 0) || (typeof v === "string" && v.trim() === ""); -/** - * ───────────────────────────────────────────────────────────────────────── - * @deprecated - * This component is being DEPRECATED in favor of src/app/(dashboard)/virtual-keys/components/KeyInfoView.tsx - * Please contribute to the new refactor. - * ───────────────────────────────────────────────────────────────────────── - */ export default function KeyInfoView({ onClose, keyData,